Elementor Page Builder - Version 3.8.0-beta3

Version Description

Download this release

Release Info

Developer KingYes
Plugin Icon 128x128 Elementor Page Builder
Version 3.8.0-beta3
Comparing to
See all releases

Code changes from version 3.7.8 to 3.8.0-beta3

Files changed (468) hide show
  1. app/app.php +272 -0
  2. app/assets/js/_app-api.scss +85 -0
  3. app/assets/js/app-context.js +20 -0
  4. app/assets/js/app-loader.js +36 -0
  5. app/assets/js/app-packages.js +19 -0
  6. app/assets/js/app.js +50 -0
  7. app/assets/js/app.scss +120 -0
  8. app/assets/js/event-track/apps-event-tracking.js +34 -0
  9. app/assets/js/hooks/use-action.js +21 -0
  10. app/assets/js/hooks/use-ajax.js +62 -0
  11. app/assets/js/hooks/use-page-title.js +11 -0
  12. app/assets/js/hooks/use-query-params.js +24 -0
  13. app/assets/js/index.js +23 -0
  14. app/assets/js/layout/content.js +16 -0
  15. app/assets/js/layout/footer.js +11 -0
  16. app/assets/js/layout/header-button.js +21 -0
  17. app/assets/js/layout/header-buttons.js +50 -0
  18. app/assets/js/layout/header.js +39 -0
  19. app/assets/js/layout/page.js +57 -0
  20. app/assets/js/layout/sidebar.js +11 -0
  21. app/assets/js/loader/commands/close.js +14 -0
  22. app/assets/js/loader/commands/index.js +5 -0
  23. app/assets/js/loader/commands/load.js +29 -0
  24. app/assets/js/loader/commands/open.js +9 -0
  25. app/assets/js/loader/component.js +37 -0
  26. app/assets/js/molecules/collapse-content.js +16 -0
  27. app/assets/js/molecules/collapse-context.js +3 -0
  28. app/assets/js/molecules/collapse-toggle.js +42 -0
  29. app/assets/js/molecules/collapse.js +54 -0
  30. app/assets/js/molecules/collapse.scss +67 -0
  31. app/assets/js/molecules/dashboard-button.js +32 -0
  32. app/assets/js/molecules/data-table.js +91 -0
  33. app/assets/js/molecules/elementor-loading.js +25 -0
  34. app/assets/js/molecules/go-pro-button.js +31 -0
  35. app/assets/js/molecules/tooltip.js +105 -0
  36. app/assets/js/molecules/upload-file.js +110 -0
  37. app/assets/js/molecules/upload-file.scss +5 -0
  38. app/assets/js/organisms/drop-zone.js +92 -0
  39. app/assets/js/organisms/drop-zone.scss +36 -0
  40. app/assets/js/organisms/error-boundary.js +58 -0
  41. app/assets/js/organisms/unfiltered-files-dialog.js +103 -0
  42. app/assets/js/organisms/wizard-footer.js +35 -0
  43. app/assets/js/organisms/wizard-footer.scss +20 -0
  44. app/assets/js/package.js +90 -0
  45. app/assets/js/pages/index.js +13 -0
  46. app/assets/js/pages/not-found.js +14 -0
  47. app/assets/js/router.js +40 -0
  48. app/assets/js/ui/atoms/box.js +35 -0
  49. app/assets/js/ui/atoms/box.scss +39 -0
  50. app/assets/js/ui/atoms/checkbox-api.scss +37 -0
  51. app/assets/js/ui/atoms/checkbox.js +49 -0
  52. app/assets/js/ui/atoms/checkbox.scss +106 -0
  53. app/assets/js/ui/atoms/css-grid.js +32 -0
  54. app/assets/js/ui/atoms/css-grid.scss +8 -0
  55. app/assets/js/ui/atoms/drag-drop.js +71 -0
  56. app/assets/js/ui/atoms/drag-drop.scss +27 -0
  57. app/assets/js/ui/atoms/heading.js +34 -0
  58. app/assets/js/ui/atoms/icon.js +13 -0
  59. app/assets/js/ui/atoms/select.js +32 -0
  60. app/assets/js/ui/atoms/text-field.js +33 -0
  61. app/assets/js/ui/atoms/text-field.scss +63 -0
  62. app/assets/js/ui/atoms/text.js +29 -0
  63. app/assets/js/ui/card/card-api.scss +65 -0
  64. app/assets/js/ui/card/card-body.js +33 -0
  65. app/assets/js/ui/card/card-divider.js +20 -0
  66. app/assets/js/ui/card/card-footer.js +33 -0
  67. app/assets/js/ui/card/card-header.js +33 -0
  68. app/assets/js/ui/card/card-headline.js +23 -0
  69. app/assets/js/ui/card/card-image.js +23 -0
  70. app/assets/js/ui/card/card-overlay.js +18 -0
  71. app/assets/js/ui/card/card.js +39 -0
  72. app/assets/js/ui/card/card.scss +94 -0
  73. app/assets/js/ui/dialog/dialog-actions.js +11 -0
  74. app/assets/js/ui/dialog/dialog-button.js +22 -0
  75. app/assets/js/ui/dialog/dialog-content.js +11 -0
  76. app/assets/js/ui/dialog/dialog-text.js +17 -0
  77. app/assets/js/ui/dialog/dialog-title.js +22 -0
  78. app/assets/js/ui/dialog/dialog-wrapper.js +33 -0
  79. app/assets/js/ui/dialog/dialog.js +69 -0
  80. app/assets/js/ui/dialog/dialog.scss +40 -0
  81. app/assets/js/ui/grid/grid-api.scss +46 -0
  82. app/assets/js/ui/grid/grid.js +91 -0
  83. app/assets/js/ui/grid/grid.scss +213 -0
  84. app/assets/js/ui/menu/menu-item.js +12 -0
  85. app/assets/js/ui/menu/menu-item.scss +114 -0
  86. app/assets/js/ui/menu/menu.js +61 -0
  87. app/assets/js/ui/menu/menu.scss +41 -0
  88. app/assets/js/ui/modal/modal-section.js +18 -0
  89. app/assets/js/ui/modal/modal-tip.js +24 -0
  90. app/assets/js/ui/modal/modal.js +148 -0
  91. app/assets/js/ui/modal/modal.scss +101 -0
  92. app/assets/js/ui/molecules/add-new-button.js +28 -0
  93. app/assets/js/ui/molecules/add-new-button.scss +33 -0
  94. app/assets/js/ui/molecules/button.js +151 -0
  95. app/assets/js/ui/molecules/buttons-api.scss +132 -0
  96. app/assets/js/ui/molecules/buttons.scss +112 -0
  97. app/assets/js/ui/molecules/inline-link.js +74 -0
  98. app/assets/js/ui/molecules/inline-link.scss +58 -0
  99. app/assets/js/ui/molecules/list-item.js +32 -0
  100. app/assets/js/ui/molecules/list.js +46 -0
  101. app/assets/js/ui/molecules/list.scss +36 -0
  102. app/assets/js/ui/molecules/notice.js +63 -0
  103. app/assets/js/ui/molecules/notice.scss +63 -0
  104. app/assets/js/ui/molecules/popover-api.scss +51 -0
  105. app/assets/js/ui/molecules/popover.js +25 -0
  106. app/assets/js/ui/molecules/popover.scss +74 -0
  107. app/assets/js/ui/molecules/select2-api.scss +151 -0
  108. app/assets/js/ui/molecules/select2.js +74 -0
  109. app/assets/js/ui/molecules/select2.scss +85 -0
  110. app/assets/js/ui/panel/panel-body.js +25 -0
  111. app/assets/js/ui/panel/panel-header.js +29 -0
  112. app/assets/js/ui/panel/panel-headline.js +20 -0
  113. app/assets/js/ui/panel/panel.js +34 -0
  114. app/assets/js/ui/panel/panel.scss +33 -0
  115. app/assets/js/ui/popover-dialog/popover-dialog.js +163 -0
  116. app/assets/js/ui/popover-dialog/popover-dialog.scss +21 -0
  117. app/assets/js/ui/table/table-body.js +14 -0
  118. app/assets/js/ui/table/table-cell.js +17 -0
  119. app/assets/js/ui/table/table-checkbox.js +60 -0
  120. app/assets/js/ui/table/table-context.js +3 -0
  121. app/assets/js/ui/table/table-row.js +14 -0
  122. app/assets/js/ui/table/table.head.js +14 -0
  123. app/assets/js/ui/table/table.js +63 -0
  124. app/assets/js/ui/table/table.scss +82 -0
  125. app/assets/js/url-actions/actions-map.js +3 -0
  126. app/assets/js/utils/utils.js +47 -0
  127. app/assets/styles/_base.scss +6 -0
  128. app/assets/styles/_common.scss +3 -0
  129. app/assets/styles/_functions.scss +5 -0
  130. app/assets/styles/_helpers.scss +0 -0
  131. app/assets/styles/_mixins.scss +4 -0
  132. app/assets/styles/_shame.scss +54 -0
  133. app/assets/styles/_tokens.scss +37 -0
  134. app/assets/styles/app-imports.scss +90 -0
  135. app/assets/styles/base/_animations.scss +12 -0
  136. app/assets/styles/base/_custom-properties.scss +24 -0
  137. app/assets/styles/base/_layout.scss +0 -0
  138. app/assets/styles/base/_reset.scss +213 -0
  139. app/assets/styles/base/_transitions.scss +0 -0
  140. app/assets/styles/base/_typography.scss +81 -0
  141. app/assets/styles/base/_utilities.scss +23 -0
  142. app/assets/styles/base/dark/_dark-custom-properties.scss +23 -0
  143. app/assets/styles/functions/_border.scss +10 -0
  144. app/assets/styles/functions/_deep-map.scss +11 -0
  145. app/assets/styles/functions/_map-collect.scss +12 -0
  146. app/assets/styles/functions/_prefix.scss +12 -0
  147. app/assets/styles/functions/_px-to-rem.scss +3 -0
  148. app/assets/styles/functions/_size.scss +10 -0
  149. app/assets/styles/mixins/_create-atom.scss +12 -0
  150. app/assets/styles/mixins/_screen-reader.scss +34 -0
  151. app/assets/styles/mixins/_text-truncate.scss +8 -0
  152. app/assets/styles/themes/_dark-tokens.scss +14 -0
  153. app/assets/styles/themes/dark/tokens/_dark-map-functions.scss +14 -0
  154. app/assets/styles/themes/dark/tokens/_dark-theme.scss +45 -0
  155. app/assets/styles/themes/dark/tokens/maps/_dark-theme-map.scss +37 -0
  156. app/assets/styles/themes/dark/tokens/maps/_dark-tints-map.scss +10 -0
  157. app/assets/styles/themes/dark/tokens/styles/_dark-shadow.scss +2 -0
  158. app/assets/styles/tokens/_colors.scss +40 -0
  159. app/assets/styles/tokens/_font.scss +22 -0
  160. app/assets/styles/tokens/_global.scss +7 -0
  161. app/assets/styles/tokens/_map-functions.scss +38 -0
  162. app/assets/styles/tokens/_opacity.scss +8 -0
  163. app/assets/styles/tokens/_ratio.scss +13 -0
  164. app/assets/styles/tokens/_spacing.scss +1 -0
  165. app/assets/styles/tokens/_theme.scss +48 -0
  166. app/assets/styles/tokens/_type.scss +53 -0
  167. app/assets/styles/tokens/_z-index.scss +8 -0
  168. app/assets/styles/tokens/maps/_atoms.scss +181 -0
  169. app/assets/styles/tokens/maps/_opacity-map.scss +11 -0
  170. app/assets/styles/tokens/maps/_shadow-map.scss +4 -0
  171. app/assets/styles/tokens/maps/_spacing-map.scss +12 -0
  172. app/assets/styles/tokens/maps/_theme-map.scss +36 -0
  173. app/assets/styles/tokens/maps/_tints-map.scss +11 -0
  174. app/assets/styles/tokens/maps/_type-map.scss +47 -0
  175. app/assets/styles/tokens/maps/_z-index-map.scss +10 -0
  176. app/assets/styles/tokens/styles/_border.scss +15 -0
  177. app/assets/styles/tokens/styles/_motion.scss +6 -0
  178. app/assets/styles/tokens/styles/_radius.scss +7 -0
  179. app/assets/styles/tokens/styles/_shadow.scss +26 -0
  180. app/modules/import-export/assets/js/admin.js +19 -0
  181. app/modules/import-export/assets/js/context/export-context/export-context-provider.js +29 -0
  182. app/modules/import-export/assets/js/context/export-context/export-context-reducer.js +18 -0
  183. app/modules/import-export/assets/js/context/import-context/import-context-provider.js +32 -0
  184. app/modules/import-export/assets/js/context/import-context/import-context-reducer.js +32 -0
  185. app/modules/import-export/assets/js/context/shared-context/shared-context-provider.js +28 -0
  186. app/modules/import-export/assets/js/context/shared-context/shared-context-reducer.js +22 -0
  187. app/modules/import-export/assets/js/context/utils/reducer-utils.js +16 -0
  188. app/modules/import-export/assets/js/export.js +27 -0
  189. app/modules/import-export/assets/js/hooks/use-kit.js +102 -0
  190. app/modules/import-export/assets/js/hooks/use-plugins-data.js +39 -0
  191. app/modules/import-export/assets/js/hooks/use-plugins.js +128 -0
  192. app/modules/import-export/assets/js/import.js +33 -0
  193. app/modules/import-export/assets/js/module.js +23 -0
  194. app/modules/import-export/assets/js/package.js +8 -0
  195. app/modules/import-export/assets/js/pages/export/export-complete/export-complete.js +66 -0
  196. app/modules/import-export/assets/js/pages/export/export-complete/export-complete.scss +5 -0
  197. app/modules/import-export/assets/js/pages/export/export-kit/components/kit-information/components/kit-description/kit-description.js +22 -0
  198. app/modules/import-export/assets/js/pages/export/export-kit/components/kit-information/components/kit-info-modal/kit-info-modal.js +18 -0
  199. app/modules/import-export/assets/js/pages/export/export-kit/components/kit-information/components/kit-name/kit-name.js +19 -0
  200. app/modules/import-export/assets/js/pages/export/export-kit/components/kit-information/kit-information.js +73 -0
  201. app/modules/import-export/assets/js/pages/export/export-kit/export-kit.js +62 -0
  202. app/modules/import-export/assets/js/pages/export/export-kit/export-kit.scss +36 -0
  203. app/modules/import-export/assets/js/pages/export/export-plugins/components/export-plugins-footer/export-plugins-footer.js +25 -0
  204. app/modules/import-export/assets/js/pages/export/export-plugins/components/export-plugins-selection/export-plugins-selection.js +48 -0
  205. app/modules/import-export/assets/js/pages/export/export-plugins/export-plugins.js +58 -0
  206. app/modules/import-export/assets/js/pages/export/export-plugins/export-plugins.scss +3 -0
  207. app/modules/import-export/assets/js/pages/export/export-process/export-process.js +79 -0
  208. app/modules/import-export/assets/js/pages/export/export-process/hooks/use-export-plugins-data.js +24 -0
  209. app/modules/import-export/assets/js/pages/import/hooks/use-import-actions.js +30 -0
  210. app/modules/import-export/assets/js/pages/import/import-complete/components/connect-pro-notice/connect-pro-notice.js +23 -0
  211. app/modules/import-export/assets/js/pages/import/import-complete/components/connect-pro-notice/connect-pro-notice.scss +3 -0
  212. app/modules/import-export/assets/js/pages/import/import-complete/components/failed-plugins-notice/failed-plugins-notice.js +30 -0
  213. app/modules/import-export/assets/js/pages/import/import-complete/components/failed-plugins-notice/failed-plugins-notice.scss +3 -0
  214. app/modules/import-export/assets/js/pages/import/import-complete/components/import-complete-footer/import-complete-footer.js +51 -0
  215. app/modules/import-export/assets/js/pages/import/import-complete/hooks/use-imported-kit-data.js +58 -0
  216. app/modules/import-export/assets/js/pages/import/import-complete/import-complete.js +95 -0
  217. app/modules/import-export/assets/js/pages/import/import-content/components/import-content-display/import-content-display.js +65 -0
  218. app/modules/import-export/assets/js/pages/import/import-content/components/import-content-footer/import-content-footer.js +53 -0
  219. app/modules/import-export/assets/js/pages/import/import-content/import-content.js +77 -0
  220. app/modules/import-export/assets/js/pages/import/import-content/import-content.scss +7 -0
  221. app/modules/import-export/assets/js/pages/import/import-kit/hooks/use-import-kit-library-apply-all-plugins.js +21 -0
  222. app/modules/import-export/assets/js/pages/import/import-kit/import-kit.js +150 -0
  223. app/modules/import-export/assets/js/pages/import/import-kit/import-kit.scss +29 -0
  224. app/modules/import-export/assets/js/pages/import/import-plugins-activation/components/plugin-status-item/plugin-status-item.js +32 -0
  225. app/modules/import-export/assets/js/pages/import/import-plugins-activation/hooks/use-install-plugins.js +104 -0
  226. app/modules/import-export/assets/js/pages/import/import-plugins-activation/import-plugins-activation.js +74 -0
  227. app/modules/import-export/assets/js/pages/import/import-plugins-activation/import-plugins-activation.scss +13 -0
  228. app/modules/import-export/assets/js/pages/import/import-plugins/components/existing-plugins/existing-plugins.js +39 -0
  229. app/modules/import-export/assets/js/pages/import/import-plugins/components/import-plugins-footer/import-plugins-footer.js +43 -0
  230. app/modules/import-export/assets/js/pages/import/import-plugins/components/plugins-to-import/plugins-to-import.js +64 -0
  231. app/modules/import-export/assets/js/pages/import/import-plugins/components/pro-banner/pro-banner.js +56 -0
  232. app/modules/import-export/assets/js/pages/import/import-plugins/components/pro-banner/pro-banner.scss +30 -0
  233. app/modules/import-export/assets/js/pages/import/import-plugins/hooks/use-import-plugins-data.js +47 -0
  234. app/modules/import-export/assets/js/pages/import/import-plugins/import-plugins.js +113 -0
  235. app/modules/import-export/assets/js/pages/import/import-plugins/import-plugins.scss +29 -0
  236. app/modules/import-export/assets/js/pages/import/import-process/import-process.js +205 -0
  237. app/modules/import-export/assets/js/pages/import/import-resolver/components/conflict/components/conflict-checkbox/conflict-checkbox.js +42 -0
  238. app/modules/import-export/assets/js/pages/import/import-resolver/components/conflict/conflict.js +95 -0
  239. app/modules/import-export/assets/js/pages/import/import-resolver/import-resolver.js +129 -0
  240. app/modules/import-export/assets/js/pages/import/import-resolver/import-resolver.scss +78 -0
  241. app/modules/import-export/assets/js/shared/actions-footer/actions-footer.js +13 -0
  242. app/modules/import-export/assets/js/shared/content-layout/content-layout.js +15 -0
  243. app/modules/import-export/assets/js/shared/content-layout/content-layout.scss +9 -0
  244. app/modules/import-export/assets/js/shared/cpt-select-box/cpt-object-to-options-array.js +11 -0
  245. app/modules/import-export/assets/js/shared/cpt-select-box/cpt-select-box.js +54 -0
  246. app/modules/import-export/assets/js/shared/file-process/file-process.js +43 -0
  247. app/modules/import-export/assets/js/shared/info-modal/export-info-modal.js +30 -0
  248. app/modules/import-export/assets/js/shared/info-modal/import-info-modal.js +50 -0
  249. app/modules/import-export/assets/js/shared/info-modal/info-modal-heading.js +24 -0
  250. app/modules/import-export/assets/js/shared/info-modal/info-modal-section.js +20 -0
  251. app/modules/import-export/assets/js/shared/info-modal/info-modal-text.js +20 -0
  252. app/modules/import-export/assets/js/shared/info-modal/info-modal-tip.js +15 -0
  253. app/modules/import-export/assets/js/shared/info-modal/info-modal.js +54 -0
  254. app/modules/import-export/assets/js/shared/info-modal/info-modal.scss +9 -0
  255. app/modules/import-export/assets/js/shared/kit-content-data/kit-content-data.js +60 -0
  256. app/modules/import-export/assets/js/shared/kit-content/components/kit-content-checkbox/kit-content-checkbox.js +36 -0
  257. app/modules/import-export/assets/js/shared/kit-content/components/templates-features/templates-features.js +43 -0
  258. app/modules/import-export/assets/js/shared/kit-content/components/templates-features/templates-features.scss +16 -0
  259. app/modules/import-export/assets/js/shared/kit-content/kit-content.js +117 -0
  260. app/modules/import-export/assets/js/shared/kit-content/kit-content.scss +41 -0
  261. app/modules/import-export/assets/js/shared/kit-data/components/included/included.js +13 -0
  262. app/modules/import-export/assets/js/shared/kit-data/components/site-area/site-area.js +30 -0
  263. app/modules/import-export/assets/js/shared/kit-data/hooks/use-kit-data.js +74 -0
  264. app/modules/import-export/assets/js/shared/kit-data/kit-data.js +75 -0
  265. app/modules/import-export/assets/js/shared/kit-data/kit-data.scss +32 -0
  266. app/modules/import-export/assets/js/shared/plugins-selection/components/plugins-table.js +88 -0
  267. app/modules/import-export/assets/js/shared/plugins-selection/components/plugins-table.scss +8 -0
  268. app/modules/import-export/assets/js/shared/plugins-selection/plugins-selection.js +67 -0
  269. app/modules/import-export/assets/js/shared/process-failed-dialog/process-failed-dialog.js +92 -0
  270. app/modules/import-export/assets/js/templates/layout.js +104 -0
  271. app/modules/import-export/assets/js/ui/loader/loader.js +24 -0
  272. app/modules/import-export/assets/js/ui/loader/loader.scss +28 -0
  273. app/modules/import-export/assets/js/ui/message-banner/message-banner.js +50 -0
  274. app/modules/import-export/assets/js/ui/message-banner/message-banner.scss +30 -0
  275. app/modules/import-export/assets/js/ui/page-header/page-header.js +56 -0
  276. app/modules/import-export/assets/js/ui/page-header/page-header.scss +39 -0
  277. app/modules/import-export/assets/js/ui/wizard-step/wizard-step.js +81 -0
  278. app/modules/import-export/assets/js/ui/wizard-step/wizard-step.scss +67 -0
  279. app/modules/import-export/compatibility/base-adapter.php +34 -0
  280. {core/app → app}/modules/import-export/compatibility/envato.php +27 -16
  281. {core/app → app}/modules/import-export/compatibility/kit-library.php +4 -4
  282. app/modules/import-export/module.php +680 -0
  283. app/modules/import-export/processes/export.php +333 -0
  284. app/modules/import-export/processes/import.php +549 -0
  285. app/modules/import-export/processes/revert.php +164 -0
  286. app/modules/import-export/runners/export/elementor-content.php +144 -0
  287. app/modules/import-export/runners/export/export-runner-base.php +27 -0
  288. app/modules/import-export/runners/export/plugins.php +29 -0
  289. app/modules/import-export/runners/export/site-settings.php +50 -0
  290. app/modules/import-export/runners/export/taxonomies.php +118 -0
  291. app/modules/import-export/runners/export/templates.php +66 -0
  292. app/modules/import-export/runners/export/wp-content.php +79 -0
  293. app/modules/import-export/runners/import/elementor-content.php +159 -0
  294. app/modules/import-export/runners/import/import-runner-base.php +39 -0
  295. app/modules/import-export/runners/import/plugins.php +70 -0
  296. app/modules/import-export/runners/import/site-settings.php +80 -0
  297. app/modules/import-export/runners/import/taxonomies.php +143 -0
  298. app/modules/import-export/runners/import/templates.php +87 -0
  299. app/modules/import-export/runners/import/wp-content.php +124 -0
  300. app/modules/import-export/runners/revert/elementor-content.php +94 -0
  301. app/modules/import-export/runners/revert/plugins.php +16 -0
  302. app/modules/import-export/runners/revert/revert-runner-base.php +24 -0
  303. app/modules/import-export/runners/revert/site-settings.php +27 -0
  304. app/modules/import-export/runners/revert/taxonomies.php +37 -0
  305. app/modules/import-export/runners/revert/templates.php +19 -0
  306. app/modules/import-export/runners/revert/wp-content.php +73 -0
  307. app/modules/import-export/runners/runner-interface.php +20 -0
  308. app/modules/import-export/usage.php +47 -0
  309. app/modules/import-export/utils.php +83 -0
  310. {core/app → app}/modules/import-export/wp-cli.php +89 -113
  311. app/modules/kit-library/assets/js/app.js +39 -0
  312. app/modules/kit-library/assets/js/components/apply-kit-dialog.js +48 -0
  313. app/modules/kit-library/assets/js/components/badge.js +22 -0
  314. app/modules/kit-library/assets/js/components/badge.scss +24 -0
  315. app/modules/kit-library/assets/js/components/collapse.js +42 -0
  316. app/modules/kit-library/assets/js/components/collapse.scss +35 -0
  317. app/modules/kit-library/assets/js/components/connect-dialog.js +39 -0
  318. app/modules/kit-library/assets/js/components/envato-promotion.js +36 -0
  319. app/modules/kit-library/assets/js/components/envato-promotion.scss +18 -0
  320. app/modules/kit-library/assets/js/components/error-screen.js +54 -0
  321. app/modules/kit-library/assets/js/components/error-screen.scss +15 -0
  322. app/modules/kit-library/assets/js/components/favorites-actions.js +59 -0
  323. app/modules/kit-library/assets/js/components/favorites-actions.scss +20 -0
  324. app/modules/kit-library/assets/js/components/filter-indication-text.js +74 -0
  325. app/modules/kit-library/assets/js/components/filter-indication-text.scss +26 -0
  326. app/modules/kit-library/assets/js/components/item-header.js +217 -0
  327. app/modules/kit-library/assets/js/components/item-header.scss +13 -0
  328. app/modules/kit-library/assets/js/components/kit-list-item.js +90 -0
  329. app/modules/kit-library/assets/js/components/kit-list-item.scss +65 -0
  330. app/modules/kit-library/assets/js/components/kit-list.js +34 -0
  331. app/modules/kit-library/assets/js/components/layout/header-back-button.js +40 -0
  332. app/modules/kit-library/assets/js/components/layout/header-back-button.scss +29 -0
  333. app/modules/kit-library/assets/js/components/layout/header.js +45 -0
  334. app/modules/kit-library/assets/js/components/layout/index.js +26 -0
  335. app/modules/kit-library/assets/js/components/page-loader.js +19 -0
  336. app/modules/kit-library/assets/js/components/page-loader.scss +8 -0
  337. app/modules/kit-library/assets/js/components/search-input.js +55 -0
  338. app/modules/kit-library/assets/js/components/search-input.scss +81 -0
  339. app/modules/kit-library/assets/js/components/sort-select.js +74 -0
  340. app/modules/kit-library/assets/js/components/sort-select.scss +61 -0
  341. app/modules/kit-library/assets/js/components/tags-filter.scss +53 -0
  342. app/modules/kit-library/assets/js/components/taxonomies-filter-list.js +108 -0
  343. app/modules/kit-library/assets/js/components/taxonomies-filter.js +63 -0
  344. app/modules/kit-library/assets/js/context/last-filter-context.js +33 -0
  345. app/modules/kit-library/assets/js/context/settings-context.js +42 -0
  346. app/modules/kit-library/assets/js/data/kits/commands-data/download-link.js +5 -0
  347. app/modules/kit-library/assets/js/data/kits/commands-data/favorites.js +5 -0
  348. app/modules/kit-library/assets/js/data/kits/commands-data/index.js +8 -0
  349. app/modules/kit-library/assets/js/data/kits/component.js +11 -0
  350. app/modules/kit-library/assets/js/data/taxonomies/commands-data/index.js +5 -0
  351. app/modules/kit-library/assets/js/data/taxonomies/component.js +11 -0
  352. app/modules/kit-library/assets/js/e-component.js +75 -0
  353. app/modules/kit-library/assets/js/hooks/use-content-types.js +58 -0
  354. app/modules/kit-library/assets/js/hooks/use-debounced-callback.js +20 -0
  355. app/modules/kit-library/assets/js/hooks/use-download-link-mutation.js +14 -0
  356. app/modules/kit-library/assets/js/hooks/use-kit-call-to-action.js +33 -0
  357. app/modules/kit-library/assets/js/hooks/use-kit-document-by-type.js +20 -0
  358. app/modules/kit-library/assets/js/hooks/use-kit-favorites-mutations.js +64 -0
  359. app/modules/kit-library/assets/js/hooks/use-kit.js +55 -0
  360. app/modules/kit-library/assets/js/hooks/use-kits.js +187 -0
  361. app/modules/kit-library/assets/js/hooks/use-selected-taxonomies.js +9 -0
  362. app/modules/kit-library/assets/js/hooks/use-taxonomies.js +34 -0
  363. app/modules/kit-library/assets/js/kit-library.js +3 -0
  364. app/modules/kit-library/assets/js/models/base-model.js +31 -0
  365. app/modules/kit-library/assets/js/models/content-type.js +19 -0
  366. app/modules/kit-library/assets/js/models/document.js +24 -0
  367. app/modules/kit-library/assets/js/models/kit.js +69 -0
  368. app/modules/kit-library/assets/js/models/taxonomy.js +38 -0
  369. app/modules/kit-library/assets/js/module.js +25 -0
  370. app/modules/kit-library/assets/js/pages/favorites/favorites.js +33 -0
  371. app/modules/kit-library/assets/js/pages/index/index-header.js +115 -0
  372. app/modules/kit-library/assets/js/pages/index/index-header.scss +20 -0
  373. app/modules/kit-library/assets/js/pages/index/index-sidebar.js +42 -0
  374. app/modules/kit-library/assets/js/pages/index/index.js +307 -0
  375. app/modules/kit-library/assets/js/pages/index/index.scss +39 -0
  376. app/modules/kit-library/assets/js/pages/overview/overview-content-group-item.js +57 -0
  377. app/modules/kit-library/assets/js/pages/overview/overview-content-group.js +28 -0
  378. app/modules/kit-library/assets/js/pages/overview/overview-sidebar.js +112 -0
  379. app/modules/kit-library/assets/js/pages/overview/overview-sidebar.scss +58 -0
  380. app/modules/kit-library/assets/js/pages/overview/overview-taxonomy-badge.js +43 -0
  381. app/modules/kit-library/assets/js/pages/overview/overview.js +91 -0
  382. app/modules/kit-library/assets/js/pages/overview/overview.scss +22 -0
  383. app/modules/kit-library/assets/js/pages/preview/preview-iframe.js +44 -0
  384. app/modules/kit-library/assets/js/pages/preview/preview-iframe.scss +9 -0
  385. app/modules/kit-library/assets/js/pages/preview/preview-responsive-controls.js +38 -0
  386. app/modules/kit-library/assets/js/pages/preview/preview-responsive-controls.scss +21 -0
  387. app/modules/kit-library/assets/js/pages/preview/preview.js +173 -0
  388. app/modules/kit-library/assets/js/pages/preview/preview.scss +20 -0
  389. app/modules/kit-library/connect/kit-library.php +72 -0
  390. {core/app → app}/modules/kit-library/data/base-controller.php +1 -1
  391. {core/app → app}/modules/kit-library/data/kits/controller.php +2 -2
  392. {core/app → app}/modules/kit-library/data/kits/endpoints/download-link.php +2 -2
  393. {core/app → app}/modules/kit-library/data/kits/endpoints/favorites.php +2 -2
  394. {core/app → app}/modules/kit-library/data/repository.php +2 -2
  395. {core/app → app}/modules/kit-library/data/taxonomies/controller.php +2 -2
  396. app/modules/kit-library/module.php +102 -0
  397. app/modules/onboarding/assets/js/app.js +46 -0
  398. app/modules/onboarding/assets/js/components/button.js +26 -0
  399. app/modules/onboarding/assets/js/components/button.scss +32 -0
  400. app/modules/onboarding/assets/js/components/card.js +32 -0
  401. app/modules/onboarding/assets/js/components/card.scss +38 -0
  402. app/modules/onboarding/assets/js/components/checkbox-with-label.js +21 -0
  403. app/modules/onboarding/assets/js/components/checklist-item.js +12 -0
  404. app/modules/onboarding/assets/js/components/checklist.js +11 -0
  405. app/modules/onboarding/assets/js/components/checklist.scss +16 -0
  406. app/modules/onboarding/assets/js/components/go-pro-popover.js +102 -0
  407. app/modules/onboarding/assets/js/components/layout/footer-buttons.js +24 -0
  408. app/modules/onboarding/assets/js/components/layout/header.js +48 -0
  409. app/modules/onboarding/assets/js/components/layout/header.scss +84 -0
  410. app/modules/onboarding/assets/js/components/layout/layout.js +127 -0
  411. app/modules/onboarding/assets/js/components/layout/layout.scss +34 -0
  412. app/modules/onboarding/assets/js/components/layout/page-content-layout.js +51 -0
  413. app/modules/onboarding/assets/js/components/new-page-kit-list-item.js +36 -0
  414. app/modules/onboarding/assets/js/components/notice.js +12 -0
  415. app/modules/onboarding/assets/js/components/notice.scss +31 -0
  416. app/modules/onboarding/assets/js/components/progress-bar/progress-bar-item.js +36 -0
  417. app/modules/onboarding/assets/js/components/progress-bar/progress-bar.js +70 -0
  418. app/modules/onboarding/assets/js/components/progress-bar/progress-bar.scss +65 -0
  419. app/modules/onboarding/assets/js/components/skip-button.js +49 -0
  420. app/modules/onboarding/assets/js/context/context.js +47 -0
  421. app/modules/onboarding/assets/js/module.js +10 -0
  422. app/modules/onboarding/assets/js/pages/account.js +193 -0
  423. app/modules/onboarding/assets/js/pages/account.scss +6 -0
  424. app/modules/onboarding/assets/js/pages/good-to-go.js +46 -0
  425. app/modules/onboarding/assets/js/pages/good-to-go.scss +31 -0
  426. app/modules/onboarding/assets/js/pages/hello-theme.js +286 -0
  427. app/modules/onboarding/assets/js/pages/hello-theme.scss +6 -0
  428. app/modules/onboarding/assets/js/pages/site-logo.js +331 -0
  429. app/modules/onboarding/assets/js/pages/site-logo.scss +66 -0
  430. app/modules/onboarding/assets/js/pages/site-name.js +123 -0
  431. app/modules/onboarding/assets/js/pages/site-name.scss +6 -0
  432. app/modules/onboarding/assets/js/pages/upload-and-install-pro.js +127 -0
  433. app/modules/onboarding/assets/js/pages/upload-and-install-pro.scss +50 -0
  434. app/modules/onboarding/assets/js/utils/connect.js +40 -0
  435. app/modules/onboarding/assets/scss/onboarding.scss +86 -0
  436. app/modules/onboarding/module.php +478 -0
  437. app/modules/site-editor/assets/js/context/template-types.js +73 -0
  438. app/modules/site-editor/assets/js/module.js +166 -0
  439. app/modules/site-editor/assets/js/molecules/site-part.js +31 -0
  440. app/modules/site-editor/assets/js/molecules/site-part.scss +32 -0
  441. app/modules/site-editor/assets/js/organisms/all-parts-button.js +28 -0
  442. app/modules/site-editor/assets/js/organisms/menu.js +41 -0
  443. app/modules/site-editor/assets/js/organisms/menu.scss +4 -0
  444. app/modules/site-editor/assets/js/organisms/site-parts.js +68 -0
  445. app/modules/site-editor/assets/js/package.js +18 -0
  446. app/modules/site-editor/assets/js/pages/not-found.js +18 -0
  447. app/modules/site-editor/assets/js/pages/promotion.js +59 -0
  448. app/modules/site-editor/assets/js/pages/promotion.scss +21 -0
  449. app/modules/site-editor/assets/js/templates/layout.js +33 -0
  450. app/modules/site-editor/assets/js/templates/site-editor.scss +6 -0
  451. app/modules/site-editor/assets/scss/admin/admin-bar.scss +5 -0
  452. app/modules/site-editor/assets/scss/loading.scss +99 -0
  453. {core/app → app}/modules/site-editor/module.php +1 -1
  454. {core/app → app}/view.php +1 -1
  455. assets/css/admin-rtl.css +23 -21
  456. assets/css/admin-rtl.min.css +2 -2
  457. assets/css/admin-top-bar-rtl.css +1 -1
  458. assets/css/admin-top-bar-rtl.min.css +1 -1
  459. assets/css/admin-top-bar.css +1 -1
  460. assets/css/admin-top-bar.min.css +1 -1
  461. assets/css/admin.css +23 -21
  462. assets/css/admin.min.css +2 -2
  463. assets/css/app-base-rtl.css +1 -1
  464. assets/css/app-base-rtl.min.css +1 -1
  465. assets/css/app-base.css +1 -1
  466. assets/css/app-base.min.css +1 -1
  467. assets/css/app-rtl.css +2 -2
  468. assets/css/app-rtl.min.css +1 -1
app/app.php ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Elementor\App;
3
+
4
+ use Elementor\Icons_Manager;
5
+ use Elementor\Modules\WebCli\Module as WebCLIModule;
6
+ use Elementor\Core\Base\App as BaseApp;
7
+ use Elementor\Core\Settings\Manager as SettingsManager;
8
+ use Elementor\Plugin;
9
+ use Elementor\TemplateLibrary\Source_Local;
10
+ use Elementor\Utils;
11
+
12
+ if ( ! defined( 'ABSPATH' ) ) {
13
+ exit; // Exit if accessed directly.
14
+ }
15
+
16
+ class App extends BaseApp {
17
+
18
+ const PAGE_ID = 'elementor-app';
19
+
20
+ /**
21
+ * Get module name.
22
+ *
23
+ * Retrieve the module name.
24
+ *
25
+ * @since 3.0.0
26
+ * @access public
27
+ *
28
+ * @return string Module name.
29
+ */
30
+ public function get_name() {
31
+ return 'app';
32
+ }
33
+
34
+ public function get_base_url() {
35
+ return admin_url( 'admin.php?page=' . self::PAGE_ID . '&ver=' . ELEMENTOR_VERSION );
36
+ }
37
+
38
+ public function register_admin_menu() {
39
+ add_submenu_page(
40
+ Source_Local::ADMIN_MENU_SLUG,
41
+ esc_html__( 'Theme Builder', 'elementor' ),
42
+ esc_html__( 'Theme Builder', 'elementor' ),
43
+ 'manage_options',
44
+ self::PAGE_ID
45
+ );
46
+ }
47
+
48
+ public function fix_submenu( $menu ) {
49
+ global $submenu;
50
+
51
+ if ( is_multisite() && is_network_admin() ) {
52
+ return $menu;
53
+ }
54
+
55
+ // Non admin role / custom wp menu.
56
+ if ( empty( $submenu[ Source_Local::ADMIN_MENU_SLUG ] ) ) {
57
+ return $menu;
58
+ }
59
+
60
+ // Hack to add a link to sub menu.
61
+ foreach ( $submenu[ Source_Local::ADMIN_MENU_SLUG ] as &$item ) {
62
+ if ( self::PAGE_ID === $item[2] ) {
63
+ $item[2] = $this->get_settings( 'menu_url' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
64
+ $item[4] = 'elementor-app-link'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
65
+ }
66
+ }
67
+
68
+ return $menu;
69
+ }
70
+
71
+ public function is_current() {
72
+ return ( ! empty( $_GET['page'] ) && self::PAGE_ID === $_GET['page'] );
73
+ }
74
+
75
+ public function admin_init() {
76
+ do_action( 'elementor/app/init', $this );
77
+
78
+ $this->enqueue_assets();
79
+
80
+ // Setup default heartbeat options
81
+ // TODO: Enable heartbeat.
82
+ add_filter( 'heartbeat_settings', function( $settings ) {
83
+ $settings['interval'] = 15;
84
+ return $settings;
85
+ } );
86
+
87
+ $this->render();
88
+ die;
89
+ }
90
+
91
+ protected function get_init_settings() {
92
+ $referer = wp_get_referer();
93
+
94
+ return [
95
+ 'menu_url' => $this->get_base_url() . '#site-editor/promotion',
96
+ 'assets_url' => ELEMENTOR_ASSETS_URL,
97
+ 'return_url' => $referer ? $referer : admin_url(),
98
+ 'hasPro' => Utils::has_pro(),
99
+ 'admin_url' => admin_url(),
100
+ 'login_url' => wp_login_url(),
101
+ 'base_url' => $this->get_base_url(),
102
+ ];
103
+ }
104
+
105
+ private function render() {
106
+ require __DIR__ . '/view.php';
107
+ }
108
+
109
+ /**
110
+ * Get Elementor UI theme preference.
111
+ *
112
+ * Retrieve the user UI theme preference as defined by editor preferences manager.
113
+ *
114
+ * @since 3.0.0
115
+ * @access private
116
+ *
117
+ * @return string Preferred UI theme.
118
+ */
119
+ private function get_elementor_ui_theme_preference() {
120
+ $editor_preferences = SettingsManager::get_settings_managers( 'editorPreferences' );
121
+
122
+ return $editor_preferences->get_model()->get_settings( 'ui_theme' );
123
+ }
124
+
125
+ /**
126
+ * Enqueue dark theme detection script.
127
+ *
128
+ * Enqueues an inline script that detects user-agent settings for dark mode and adds a complimentary class to the body tag.
129
+ *
130
+ * @since 3.0.0
131
+ * @access private
132
+ */
133
+ private function enqueue_dark_theme_detection_script() {
134
+ if ( 'auto' === $this->get_elementor_ui_theme_preference() ) {
135
+ wp_add_inline_script( 'elementor-app',
136
+ 'if ( window.matchMedia && window.matchMedia( `(prefers-color-scheme: dark)` ).matches )
137
+ { document.body.classList.add( `eps-theme-dark` ); }' );
138
+ }
139
+ }
140
+
141
+ private function enqueue_assets() {
142
+ Plugin::$instance->init_common();
143
+
144
+ /** @var WebCLIModule $web_cli */
145
+ $web_cli = Plugin::$instance->modules_manager->get_modules( 'web-cli' );
146
+ $web_cli->register_scripts();
147
+
148
+ Plugin::$instance->common->register_scripts();
149
+
150
+ wp_register_style(
151
+ 'select2',
152
+ $this->get_css_assets_url( 'e-select2', 'assets/lib/e-select2/css/' ),
153
+ [],
154
+ '4.0.6-rc.1'
155
+ );
156
+
157
+ wp_register_style(
158
+ 'elementor-icons',
159
+ $this->get_css_assets_url( 'elementor-icons', 'assets/lib/eicons/css/' ),
160
+ [],
161
+ Icons_Manager::ELEMENTOR_ICONS_VERSION
162
+ );
163
+
164
+ wp_register_style(
165
+ 'elementor-common',
166
+ $this->get_css_assets_url( 'common', null, 'default', true ),
167
+ [],
168
+ ELEMENTOR_VERSION
169
+ );
170
+
171
+ wp_register_style(
172
+ 'select2',
173
+ ELEMENTOR_ASSETS_URL . 'lib/e-select2/css/e-select2.css',
174
+ [],
175
+ '4.0.6-rc.1'
176
+ );
177
+
178
+ wp_enqueue_style(
179
+ 'elementor-app',
180
+ $this->get_css_assets_url( 'app', null, 'default', true ),
181
+ [
182
+ 'select2',
183
+ 'elementor-icons',
184
+ 'elementor-common',
185
+ 'select2',
186
+ ],
187
+ ELEMENTOR_VERSION
188
+ );
189
+
190
+ wp_enqueue_script(
191
+ 'elementor-app-packages',
192
+ $this->get_js_assets_url( 'app-packages' ),
193
+ [
194
+ 'wp-i18n',
195
+ 'react',
196
+ ],
197
+ ELEMENTOR_VERSION,
198
+ true
199
+ );
200
+
201
+ wp_register_script(
202
+ 'select2',
203
+ $this->get_js_assets_url( 'e-select2.full', 'assets/lib/e-select2/js/' ),
204
+ [
205
+ 'jquery',
206
+ ],
207
+ '4.0.6-rc.1',
208
+ true
209
+ );
210
+
211
+ wp_enqueue_script(
212
+ 'elementor-app',
213
+ $this->get_js_assets_url( 'app' ),
214
+ [
215
+ 'wp-url',
216
+ 'wp-i18n',
217
+ 'react',
218
+ 'react-dom',
219
+ 'select2',
220
+ ],
221
+ ELEMENTOR_VERSION,
222
+ true
223
+ );
224
+
225
+ if ( ! $this->get_settings( 'disable_dark_theme' ) ) {
226
+ $this->enqueue_dark_theme_detection_script();
227
+ }
228
+
229
+ wp_set_script_translations( 'elementor-app-packages', 'elementor' );
230
+ wp_set_script_translations( 'elementor-app', 'elementor' );
231
+
232
+ $this->print_config();
233
+ }
234
+
235
+ public function enqueue_app_loader() {
236
+ wp_enqueue_script(
237
+ 'elementor-app-loader',
238
+ $this->get_js_assets_url( 'app-loader' ),
239
+ [
240
+ 'elementor-common',
241
+ ],
242
+ ELEMENTOR_VERSION,
243
+ true
244
+ );
245
+
246
+ $this->print_config( 'elementor-app-loader' );
247
+ }
248
+
249
+ public function __construct() {
250
+ $this->add_component( 'site-editor', new Modules\SiteEditor\Module() );
251
+
252
+ if ( current_user_can( 'manage_options' ) && Plugin::$instance->experiments->is_feature_active( 'e_import_export' ) || Utils::is_wp_cli() ) {
253
+ $this->add_component( 'import-export', new Modules\ImportExport\Module() );
254
+
255
+ // Kit library is depended on import-export
256
+ $this->add_component( 'kit-library', new Modules\KitLibrary\Module() );
257
+ }
258
+
259
+ $this->add_component( 'onboarding', new Modules\Onboarding\Module() );
260
+
261
+ add_action( 'admin_menu', [ $this, 'register_admin_menu' ], 21 /* after Elementor page */ );
262
+
263
+ // Happens after WP plugin page validation.
264
+ add_filter( 'add_menu_classes', [ $this, 'fix_submenu' ] );
265
+
266
+ if ( $this->is_current() ) {
267
+ add_action( 'admin_init', [ $this, 'admin_init' ], 0 );
268
+ } else {
269
+ add_action( 'elementor/common/after_register_scripts', [ $this, 'enqueue_app_loader' ] );
270
+ }
271
+ }
272
+ }
app/assets/js/_app-api.scss ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // APP : private API
2
+ // - local tokens mapped to EPS global tokens
3
+
4
+ $app-background-color: tints(200);
5
+ $spp-font-size: type(text, base);
6
+ $app-box-shadow-color: $eps-box-shadow-size-3;
7
+ $app-radius: 0;
8
+ $app-max-width: 100%;
9
+
10
+ $app-header-background-color: theme-colors(light);
11
+ $app-header-box-shadow: $eps-box-shadow-2;
12
+ $app-header-height: px-to-rem(50);
13
+ $app-header-color: tints(800);
14
+ $app-header-font-size: type(size, "15");
15
+ $spp-header-padding-x: spacing(16);
16
+ $app-header-padding-y: 0;
17
+ $app-header-z-index: 3;
18
+
19
+ $app-main-height: 100%;
20
+
21
+ $app-sidebar-background-color: rgba(theme-colors(light), $opacity-05);
22
+ $app-sidebar-padding-top: spacing(20);
23
+ $app-sidebar-width: 30%;
24
+ $app-sidebar-max-width: px-to-rem(275);
25
+ $app-sidebar-box-shadow: $eps-box-shadow-3;
26
+ $app-sidebar-z-index: 4;
27
+
28
+ $app-logo-size: px-to-rem(28);
29
+ $app-logo-font-size: calc(0.4 * #{$app-logo-size});
30
+ $app-logo-radius: 50%;
31
+ $app-logo-color: theme-colors(light);
32
+ $app-logo-background-color: theme-colors(cta);
33
+ $app-logo-margin-end: spacing(10);
34
+
35
+ $app-header-buttons-separator-color: tints(300);
36
+ $app-header-buttons-color: tints(500);
37
+ $app-header-buttons-font-size: type(size, "18");
38
+
39
+ $app-header-btn-padding-x: spacing(16);
40
+
41
+ $app-header-btn-font-size: type(size, "18");
42
+ $app-header-btn-line-height: spacing(20);
43
+
44
+ $app-title-font-size: type(text, lg);
45
+ $app-title-font-weight: $eps-font-weight-bold;
46
+
47
+ $app-content-padding: spacing(44);
48
+
49
+ $app-lightbox-background-color: rgba($eps-dark-theme-dark, $opacity-08);
50
+ $app-lightbox-z-index: $eps-zindex-modal;
51
+
52
+ // dark theme tokens
53
+ $app-dark-background-color: dark-tints(700);
54
+ $app-dark-box-shadow-color: rgba(var(--box-shadow-color, #{$eps-dark-box-shadow-color}), 4 * $eps-box-shadow-alpha-unit);
55
+ $app-dark-header-background-color: dark-tints(800);
56
+ $app-dark-header-color: dark-tints(100);
57
+
58
+ $app-dark-header-buttons-separator-color: dark-tints(400);
59
+ $app-dark-header-buttons-color: dark-tints(200);
60
+
61
+ $app-dark-sidebar-background-color: dark-tints(700);
62
+
63
+ $app-dark-lightbox-background-color: rgba(dark-theme-colors(dark), $opacity-08);
64
+
65
+ :root {
66
+ --app-background-color: #{$app-background-color};
67
+ --app-box-shadow-color: #{$app-dark-box-shadow-color};
68
+ --app-header-background-color: #{$app-header-background-color};
69
+ --app-header-color: #{$app-header-color};
70
+ --app-sidebar-background-color: #{$app-sidebar-background-color};
71
+ --app-header-buttons-separator-color: #{$app-header-buttons-separator-color};
72
+ --app-header-buttons-color: #{$app-header-buttons-color};
73
+ --app-lightbox-background-color: #{$app-lightbox-background-color};
74
+ }
75
+
76
+ .eps-theme-dark {
77
+ --app-background-color: #{$app-dark-background-color};
78
+ --app-box-shadow-color: #{$app-dark-box-shadow-color};
79
+ --app-header-background-color: #{$app-dark-header-background-color};
80
+ --app-header-color: #{$app-dark-header-color};
81
+ --app-sidebar-background-color: #{$app-dark-sidebar-background-color};
82
+ --app-header-buttons-separator-color: #{$app-dark-header-buttons-separator-color};
83
+ --app-header-buttons-color: #{$app-dark-header-buttons-color};
84
+ --app-lightbox-background-color: #{$app-dark-lightbox-background-color};
85
+ }
app/assets/js/app-context.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+
3
+ export const AppContext = React.createContext();
4
+
5
+ export default function AppProvider( props ) {
6
+ const initialState = {
7
+ isDarkMode: document.body.classList.contains( `eps-theme-dark` ),
8
+ },
9
+ [ state, setState ] = useState( initialState );
10
+
11
+ return (
12
+ <AppContext.Provider value={ { state, setState } }>
13
+ { props.children }
14
+ </AppContext.Provider>
15
+ );
16
+ }
17
+
18
+ AppProvider.propTypes = {
19
+ children: PropTypes.object.isRequired,
20
+ };
app/assets/js/app-loader.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Component from './loader/component';
2
+
3
+ class AppLoader {
4
+ selector = 'a.elementor-app-link, .elementor-app-link .ab-item';
5
+
6
+ constructor() {
7
+ $e.components.register( new Component() );
8
+
9
+ window.addEventListener( 'DOMContentLoaded', this.onLoad.bind( this ) );
10
+ }
11
+
12
+ onLoad() {
13
+ const links = document.querySelectorAll( this.selector );
14
+
15
+ if ( ! links.length ) {
16
+ return;
17
+ }
18
+
19
+ links.forEach( ( link ) => {
20
+ link.addEventListener( 'click', ( event ) => {
21
+ event.preventDefault();
22
+ $e.run( 'app/open', {
23
+ url: link.href,
24
+ } );
25
+ } );
26
+
27
+ link.addEventListener( 'mouseenter', () => {
28
+ $e.run( 'app/load', {
29
+ url: link.href,
30
+ } );
31
+ } );
32
+ } );
33
+ }
34
+ }
35
+
36
+ window.elementorAppLoader = new AppLoader();
app/assets/js/app-packages.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Temporary solution for share components.
3
+ * TODO.
4
+ */
5
+
6
+ // Make router available for use within packages.
7
+ import router from './router';
8
+
9
+ // Alphabetical order.
10
+ import { appUi, components, hooks } from './package';
11
+ import siteEditor from '../../modules/site-editor/assets/js/package';
12
+
13
+ window.elementorAppPackages = {
14
+ appUi,
15
+ components,
16
+ hooks,
17
+ router,
18
+ siteEditor,
19
+ };
app/assets/js/app.js ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Elementor App
3
+ */
4
+ import { useContext } from 'react';
5
+ import router from '@elementor/router';
6
+ import { Router, LocationProvider, createHistory } from '@reach/router';
7
+ import { createHashSource } from 'reach-router-hash-history';
8
+ import NotFound from 'elementor-app/pages/not-found';
9
+ import Index from 'elementor-app/pages/index';
10
+ import ErrorBoundary from 'elementor-app/organisms/error-boundary';
11
+ import './app.scss';
12
+
13
+ import { AppContext } from 'elementor-app/app-context';
14
+
15
+ import { ThemeProvider } from 'styled-components';
16
+
17
+ const { Suspense } = React;
18
+
19
+ export default function App() {
20
+ const appContext = useContext( AppContext ),
21
+ { isDarkMode } = appContext.state,
22
+ theme = {
23
+ config: {
24
+ variants: {
25
+ light: ! isDarkMode,
26
+ dark: isDarkMode,
27
+ },
28
+ },
29
+ };
30
+
31
+ // Use hash route because it's actually rendered on a WP Admin page.
32
+ // Make it public for external uses.
33
+ router.appHistory = createHistory( createHashSource() );
34
+
35
+ return (
36
+ <ErrorBoundary>
37
+ <LocationProvider history={ router.appHistory }>
38
+ <ThemeProvider theme={ theme }>
39
+ <Suspense fallback={ null }>
40
+ <Router>
41
+ { router.getRoutes() }
42
+ <Index path="/" />
43
+ <NotFound default />
44
+ </Router>
45
+ </Suspense>
46
+ </ThemeProvider>
47
+ </LocationProvider>
48
+ </ErrorBoundary>
49
+ );
50
+ }
app/assets/js/app.scss ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "./app-api";
2
+ @import "../styles/base";
3
+
4
+ .#{$eps-prefix}app {
5
+ display: flex;
6
+ height: 100vh;
7
+ flex-direction: column;
8
+ color: var(--body-color);
9
+ background-color: var(--app-background-color);
10
+ position: absolute;
11
+ border-radius: $app-radius;
12
+ box-shadow: $eps-box-shadow-3;
13
+ overflow: hidden;
14
+ font-family: $eps-font-family; // todo: we have body definition for font familiy,consider removing
15
+ width: 100%;
16
+ max-width: $app-max-width;
17
+
18
+ &__lightbox {
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ position: fixed;
23
+ height: 100%;
24
+ width: 100%;
25
+ background-color: var(--app-lightbox-background-color);
26
+ z-index: $app-lightbox-z-index;
27
+ bottom: 0;
28
+ left: 0;
29
+ }
30
+
31
+ &__header {
32
+ flex-shrink: 0;
33
+ font-size: $app-header-font-size;
34
+ color: var(--app-header-color);
35
+ background-color: var(--app-header-background-color);
36
+ box-shadow: $app-header-box-shadow;
37
+ position: relative;
38
+ z-index: $app-header-z-index;
39
+ height: $app-header-height;
40
+ padding: $app-header-padding-y $spp-header-padding-x;
41
+ }
42
+
43
+ &__header-buttons {
44
+ display: flex;
45
+ align-items: center;
46
+ flex-direction: row-reverse;
47
+ font-size: $app-header-buttons-font-size;
48
+ }
49
+
50
+ &__header-btn {
51
+ --button-background-color: var(--app-header-buttons-color);
52
+ padding-inline-start: $app-header-btn-padding-x;
53
+ font-size: $app-header-btn-font-size;
54
+ line-height: $app-header-btn-line-height;
55
+
56
+ &:first-child {
57
+ border-inline-start: 1px solid var(--app-header-buttons-separator-color);
58
+ }
59
+
60
+ &:not(:first-child) {
61
+ padding-inline-end: $app-header-btn-padding-x;
62
+ }
63
+ }
64
+
65
+
66
+ &__logo-title-wrapper {
67
+ display: flex;
68
+ align-items: center;
69
+ }
70
+
71
+ //todo: consider refactoring as a stand alone block.
72
+ &__logo {
73
+ display: block;
74
+ width: $app-logo-size;
75
+ height: $app-logo-size;
76
+ line-height: $app-logo-size;
77
+ text-align: center;
78
+ font-size: $app-logo-font-size;
79
+ border-radius: $app-logo-radius;
80
+ color: $app-logo-color;
81
+ background-color: $app-logo-background-color;
82
+
83
+ &:not(:last-child) {
84
+ margin-inline-end: $app-logo-margin-end;
85
+ }
86
+ }
87
+
88
+ //todo: consider refactoring as logo element.
89
+ &__title {
90
+ font-size: $app-title-font-size;
91
+ font-weight: $app-title-font-weight;
92
+ text-transform: uppercase;
93
+ margin-bottom: 0;
94
+ }
95
+
96
+ &__main {
97
+ display: flex;
98
+ overflow: hidden;
99
+ flex-grow: 1;
100
+ }
101
+
102
+ &__sidebar {
103
+ background-color: var(--app-sidebar-background-color);
104
+ padding-top: $app-sidebar-padding-top;
105
+ width: $app-sidebar-width;
106
+ max-width: $app-sidebar-max-width;
107
+ flex-grow: 0;
108
+ overflow-y: auto;
109
+ box-shadow: $app-sidebar-box-shadow;
110
+ z-index: $app-sidebar-z-index;
111
+ }
112
+
113
+ &__content {
114
+ flex-grow: 1;
115
+ position: relative;
116
+ padding: $app-content-padding;
117
+ height: 100%;
118
+ overflow-y: auto;
119
+ }
120
+ }
app/assets/js/event-track/apps-event-tracking.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const appsEventTrackingDispatch = ( command, eventParams ) => {
2
+ // Add existing eventParams key value pair to the data/details object.
3
+ const objectCreator = ( array, obj ) => {
4
+ for ( const key of array ) {
5
+ if ( eventParams.hasOwnProperty( key ) && eventParams[ key ] !== null ) {
6
+ obj[ key ] = eventParams[ key ];
7
+ }
8
+ }
9
+ return obj;
10
+ };
11
+
12
+ const dataKeys = [];
13
+ const detailsKeys = [ 'layout', 'site_part', 'error', 'document_name', 'document_type', 'view_type_clicked', 'tag', 'sort_direction', 'sort_type', 'action', 'grid_location', 'kit_name', 'page_source', 'element_position', 'element', 'event_type', 'modal_type', 'method', 'status', 'step', 'item', 'category', 'element_location', 'search_term', 'section', 'site_area' ];
14
+ const data = {};
15
+ const details = {};
16
+
17
+ const init = () => {
18
+ objectCreator( detailsKeys, details );
19
+ objectCreator( dataKeys, data );
20
+
21
+ const commandSplit = command.split( '/' );
22
+ data.placement = commandSplit[ 0 ];
23
+ data.event = commandSplit[ 1 ];
24
+
25
+ // If 'details' is not empty, add the details object to the data object.
26
+ if ( Object.keys( details ).length ) {
27
+ data.details = details;
28
+ }
29
+ };
30
+
31
+ init();
32
+
33
+ $e.run( command, data );
34
+ };
app/assets/js/hooks/use-action.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function useAction() {
2
+ return {
3
+ backToDashboard: () => {
4
+ if ( window.top === window ) {
5
+ window.top.location = elementorAppConfig.admin_url;
6
+ } else {
7
+ // Iframe.
8
+ window.top.$e.run( 'app/close' );
9
+ }
10
+ },
11
+ backToReferrer: () => {
12
+ if ( window.top === window ) {
13
+ // Directly - in case that the return_url is the login-page, the target should be the admin-page and not the login-page again.
14
+ window.top.location = elementorAppConfig.return_url.includes( elementorAppConfig.login_url ) ? elementorAppConfig.admin_url : elementorAppConfig.return_url;
15
+ } else {
16
+ // Iframe.
17
+ window.top.$e.run( 'app/close' );
18
+ }
19
+ },
20
+ };
21
+ }
app/assets/js/hooks/use-ajax.js ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react';
2
+
3
+ export default function useAjax() {
4
+ const [ ajax, setAjax ] = useState( null ),
5
+ initialStatusKey = 'initial',
6
+ uploadInitialState = {
7
+ status: initialStatusKey,
8
+ isComplete: false,
9
+ response: null,
10
+ },
11
+ [ ajaxState, setAjaxState ] = useState( uploadInitialState ),
12
+ ajaxActions = {
13
+ reset: () => setAjaxState( initialStatusKey ),
14
+ };
15
+
16
+ useEffect( () => {
17
+ if ( ajax ) {
18
+ const formData = new FormData();
19
+
20
+ if ( ajax.data ) {
21
+ for ( const key in ajax.data ) {
22
+ formData.append( key, ajax.data[ key ] );
23
+ }
24
+
25
+ if ( ! ajax.data.nonce ) {
26
+ formData.append( '_nonce', elementorCommon.config.ajax.nonce );
27
+ }
28
+ }
29
+
30
+ const options = {
31
+ type: 'post',
32
+ url: elementorCommon.config.ajax.url,
33
+ headers: {},
34
+ cache: false,
35
+ contentType: false,
36
+ processData: false,
37
+ ...ajax,
38
+ data: formData,
39
+ success: ( response ) => {
40
+ const status = response.success ? 'success' : 'error';
41
+
42
+ setAjaxState( ( prevState ) => ( { ...prevState, status, response: response?.data } ) );
43
+ },
44
+ error: () => {
45
+ setAjaxState( ( prevState ) => ( { ...prevState, status: 'error' } ) );
46
+ },
47
+ complete: () => {
48
+ setAjaxState( ( prevState ) => ( { ...prevState, isComplete: true } ) );
49
+ },
50
+ };
51
+
52
+ jQuery.ajax( options );
53
+ }
54
+ }, [ ajax ] );
55
+
56
+ return {
57
+ ajax,
58
+ setAjax,
59
+ ajaxState,
60
+ ajaxActions,
61
+ };
62
+ }
app/assets/js/hooks/use-page-title.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect } from 'react';
2
+
3
+ export default function usePageTitle( { title, prefix } ) {
4
+ useEffect( () => {
5
+ if ( ! prefix ) {
6
+ prefix = __( 'Elementor', 'elementor' );
7
+ }
8
+
9
+ document.title = `${ prefix } | ${ title }`;
10
+ }, [ title, prefix ] );
11
+ }
app/assets/js/hooks/use-query-params.js ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function useQueryParams() {
2
+ const urlSearchParams = new URLSearchParams( window.location.search ),
3
+ urlParams = Object.fromEntries( urlSearchParams.entries() ),
4
+ hashValue = location.hash.match( /\?(.+)/ )?.[ 1 ],
5
+ hashParams = {};
6
+
7
+ if ( hashValue ) {
8
+ hashValue.split( '&' ).forEach( ( pair ) => {
9
+ const [ key, value ] = pair.split( '=' );
10
+
11
+ hashParams[ key ] = value;
12
+ } );
13
+ }
14
+
15
+ // Merging the URL params with the hash params.
16
+ const queryParams = {
17
+ ...urlParams,
18
+ ...hashParams,
19
+ };
20
+
21
+ return {
22
+ getAll: () => queryParams,
23
+ };
24
+ }
app/assets/js/index.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import App from './app';
2
+ import ImportExport from '../../modules/import-export/assets/js/module';
3
+ import KitLibrary from '../../modules/kit-library/assets/js/module';
4
+ import Onboarding from '../../modules/onboarding/assets/js/module';
5
+ import { Module as SiteEditor } from '@elementor/site-editor';
6
+
7
+ import AppProvider from './app-context';
8
+
9
+ new ImportExport();
10
+ new KitLibrary();
11
+ new SiteEditor();
12
+ new Onboarding();
13
+
14
+ const AppWrapper = React.Fragment;
15
+
16
+ ReactDOM.render(
17
+ <AppWrapper>
18
+ <AppProvider>
19
+ <App />
20
+ </AppProvider>
21
+ </AppWrapper>,
22
+ document.getElementById( 'e-app' ),
23
+ );
app/assets/js/layout/content.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function Content( props ) {
2
+ return (
3
+ <main className={ `eps-app__content ${ props.className }` }>
4
+ { props.children }
5
+ </main>
6
+ );
7
+ }
8
+
9
+ Content.propTypes = {
10
+ children: PropTypes.any,
11
+ className: PropTypes.string,
12
+ };
13
+
14
+ Content.defaultProps = {
15
+ className: '',
16
+ };
app/assets/js/layout/footer.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function Footer( props ) {
2
+ return (
3
+ <footer className="eps-app__footer">
4
+ { props.children }
5
+ </footer>
6
+ );
7
+ }
8
+
9
+ Footer.propTypes = {
10
+ children: PropTypes.object,
11
+ };
app/assets/js/layout/header-button.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import BaseButton from '../ui/molecules/button';
2
+
3
+ export default class Button extends BaseButton {
4
+ static defaultProps = Object.assign( {} /* Clone */, BaseButton.defaultProps, {
5
+ hideText: true,
6
+ includeHeaderBtnClass: true,
7
+ } );
8
+
9
+ getCssId() {
10
+ return `eps-app-header-btn-` + super.getCssId();
11
+ }
12
+
13
+ getClassName() {
14
+ // Avoid using the 'eps-app__header-btn' class to make sure it is not override custom styles.
15
+ if ( ! this.props.includeHeaderBtnClass ) {
16
+ return super.getClassName();
17
+ }
18
+
19
+ return `eps-app__header-btn ` + super.getClassName();
20
+ }
21
+ }
app/assets/js/layout/header-buttons.js ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import useAction from 'elementor-app/hooks/use-action';
2
+
3
+ import Button from './header-button';
4
+
5
+ export default function HeaderButtons( props ) {
6
+ const action = useAction();
7
+
8
+ const actionOnClose = () => {
9
+ if ( props.onClose ) {
10
+ props.onClose();
11
+ } else {
12
+ action.backToDashboard();
13
+ }
14
+ };
15
+
16
+ let tools = '';
17
+
18
+ if ( props.buttons.length ) {
19
+ const buttons = props.buttons.map( ( button ) => {
20
+ return <Button key={ button.id } { ...button } />;
21
+ } );
22
+
23
+ tools = (
24
+ <>
25
+ { buttons }
26
+ </>
27
+ );
28
+ }
29
+
30
+ return (
31
+ <div className="eps-app__header-buttons">
32
+ <Button
33
+ text={ __( 'Close', 'elementor' ) }
34
+ icon="eicon-close"
35
+ className="eps-app__close-button"
36
+ onClick={ actionOnClose }
37
+ />
38
+ { tools }
39
+ </div>
40
+ );
41
+ }
42
+
43
+ HeaderButtons.propTypes = {
44
+ buttons: PropTypes.arrayOf( PropTypes.object ),
45
+ onClose: PropTypes.func,
46
+ };
47
+
48
+ HeaderButtons.defaultProps = {
49
+ buttons: [],
50
+ };
app/assets/js/layout/header.js ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Grid from '../ui/grid/grid';
2
+ import HeaderButtons from './header-buttons';
3
+ import usePageTitle from 'elementor-app/hooks/use-page-title';
4
+
5
+ export default function Header( props ) {
6
+ usePageTitle( { title: props.title } );
7
+
8
+ let TitleTag = 'span',
9
+ titleAttrs = {};
10
+
11
+ if ( props.titleRedirectRoute ) {
12
+ TitleTag = 'a';
13
+ titleAttrs = {
14
+ href: `#${ props.titleRedirectRoute }`,
15
+ target: '_self',
16
+ };
17
+ }
18
+
19
+ return (
20
+ <Grid container alignItems="center" justify="space-between" className="eps-app__header">
21
+ <TitleTag className="eps-app__logo-title-wrapper" { ...titleAttrs }>
22
+ <i className="eps-app__logo eicon-elementor" />
23
+ <h1 className="eps-app__title">{ props.title }</h1>
24
+ </TitleTag>
25
+ <HeaderButtons buttons={ props.buttons } />
26
+ </Grid>
27
+ );
28
+ }
29
+
30
+ Header.propTypes = {
31
+ title: PropTypes.string,
32
+ titleRedirectRoute: PropTypes.string,
33
+ buttons: PropTypes.arrayOf( PropTypes.object ),
34
+ onClose: PropTypes.func,
35
+ };
36
+
37
+ Header.defaultProps = {
38
+ buttons: [],
39
+ };
app/assets/js/layout/page.js ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Header from './header';
2
+ import Sidebar from './sidebar';
3
+ import Content from './content';
4
+ import Footer from './footer';
5
+
6
+ export default function Page( props ) {
7
+ const AppSidebar = () => {
8
+ if ( ! props.sidebar ) {
9
+ return;
10
+ }
11
+ return (
12
+ <Sidebar>
13
+ { props.sidebar }
14
+ </Sidebar>
15
+ );
16
+ },
17
+ AppFooter = () => {
18
+ if ( ! props.footer ) {
19
+ return;
20
+ }
21
+ return (
22
+ <Footer>
23
+ { props.footer }
24
+ </Footer>
25
+ );
26
+ };
27
+
28
+ return (
29
+ <div className={ `eps-app__lightbox ${ props.className }` }>
30
+ <div className="eps-app">
31
+ <Header title={ props.title } buttons={ props.headerButtons } titleRedirectRoute={ props.titleRedirectRoute } onClose={ () => props.onClose?.() } />
32
+ <div className="eps-app__main">
33
+ { AppSidebar() }
34
+ <Content>
35
+ { props.content }
36
+ </Content>
37
+ </div>
38
+ { AppFooter() }
39
+ </div>
40
+ </div>
41
+ );
42
+ }
43
+
44
+ Page.propTypes = {
45
+ title: PropTypes.string,
46
+ titleRedirectRoute: PropTypes.string,
47
+ className: PropTypes.string,
48
+ headerButtons: PropTypes.arrayOf( PropTypes.object ),
49
+ sidebar: PropTypes.object,
50
+ content: PropTypes.object.isRequired,
51
+ footer: PropTypes.object,
52
+ onClose: PropTypes.func,
53
+ };
54
+
55
+ Page.defaultProps = {
56
+ className: '',
57
+ };
app/assets/js/layout/sidebar.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function Sidebar( props ) {
2
+ return (
3
+ <div className="eps-app__sidebar">
4
+ { props.children }
5
+ </div>
6
+ );
7
+ }
8
+
9
+ Sidebar.propTypes = {
10
+ children: PropTypes.object,
11
+ };
app/assets/js/loader/commands/close.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export class Close extends $e.modules.CommandBase {
2
+ apply() {
3
+ if ( ! this.component.close() ) {
4
+ return false;
5
+ }
6
+
7
+ this.component.iframe.remove();
8
+ this.component.iframe = null;
9
+
10
+ return true;
11
+ }
12
+ }
13
+
14
+ export default Close;
app/assets/js/loader/commands/index.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ // Alphabetical order.
2
+
3
+ export { Close } from './close';
4
+ export { Load } from './load';
5
+ export { Open } from './open';
app/assets/js/loader/commands/load.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export class Load extends $e.modules.CommandBase {
2
+ apply( args ) {
3
+ const component = this.component;
4
+
5
+ if ( ! component.iframe ) {
6
+ component.iframe = document.createElement( 'iframe' );
7
+ component.iframe.className = 'elementor-app-iframe';
8
+ component.iframe.style.cssText = '' +
9
+ 'display: none;' +
10
+ 'width: 100%;' +
11
+ 'height: 100%;' +
12
+ 'position: fixed;' +
13
+ 'top: 0;' +
14
+ 'left: 0;' +
15
+ 'z-index: 99999; /* Over WP Admin Bar */' +
16
+ 'background-color: rgba(0, 0, 0, 0.8);';
17
+
18
+ document.body.appendChild( component.iframe );
19
+ }
20
+
21
+ if ( args.url === component.iframe.src ) {
22
+ return;
23
+ }
24
+
25
+ component.iframe.src = args.url;
26
+ }
27
+ }
28
+
29
+ export default Load;
app/assets/js/loader/commands/open.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ export class Open extends $e.modules.CommandBase {
2
+ apply( args ) {
3
+ $e.route( 'app', args );
4
+
5
+ return true;
6
+ }
7
+ }
8
+
9
+ export default Open;
app/assets/js/loader/component.js ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ComponentModalBase from 'elementor-api/modules/component-base';
2
+ import * as commands from './commands/';
3
+
4
+ export default class Component extends ComponentModalBase {
5
+ getNamespace() {
6
+ return 'app';
7
+ }
8
+
9
+ defaultRoutes() {
10
+ return {
11
+ '': ( args ) => {
12
+ args.url = args.url || elementorAppConfig.menu_url;
13
+
14
+ $e.run( 'app/load', args );
15
+
16
+ this.iframe.style.display = '';
17
+ document.body.style.overflow = 'hidden';
18
+ },
19
+ };
20
+ }
21
+
22
+ defaultCommands() {
23
+ return this.importCommands( commands );
24
+ }
25
+
26
+ defaultShortcuts() {
27
+ return {
28
+ '': {
29
+ keys: 'ctrl+shift+e',
30
+ },
31
+ close: {
32
+ keys: 'esc',
33
+ scopes: [ this.getNamespace() ],
34
+ },
35
+ };
36
+ }
37
+ }
app/assets/js/molecules/collapse-content.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function CollapseContent( props ) {
2
+ return (
3
+ <div className="e-app-collapse-content">
4
+ { props.children }
5
+ </div>
6
+ );
7
+ }
8
+
9
+ CollapseContent.propTypes = {
10
+ className: PropTypes.string,
11
+ children: PropTypes.any,
12
+ };
13
+
14
+ CollapseContent.defaultProps = {
15
+ className: '',
16
+ };
app/assets/js/molecules/collapse-context.js ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ import React from 'react';
2
+
3
+ export const CollapseContext = React.createContext();
app/assets/js/molecules/collapse-toggle.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+ import { arrayToClassName, pxToRem } from 'elementor-app/utils/utils.js';
3
+
4
+ import { CollapseContext } from './collapse-context';
5
+
6
+ export default function CollapseToggle( props ) {
7
+ const context = useContext( CollapseContext ),
8
+ style = { '--e-app-collapse-toggle-icon-spacing': pxToRem( props.iconSpacing ) },
9
+ classNameBase = 'e-app-collapse-toggle',
10
+ classes = [ classNameBase, { [ classNameBase + '--active' ]: props.active } ],
11
+ attrs = {
12
+ style,
13
+ className: arrayToClassName( classes ),
14
+ };
15
+
16
+ if ( props.active ) {
17
+ attrs.onClick = () => context.toggle();
18
+ }
19
+
20
+ return (
21
+ <div { ...attrs }>
22
+ { props.children }
23
+
24
+ { props.active && props.showIcon && <i className="eicon-caret-down e-app-collapse-toggle__icon" /> }
25
+ </div>
26
+ );
27
+ }
28
+
29
+ CollapseToggle.propTypes = {
30
+ className: PropTypes.string,
31
+ iconSpacing: PropTypes.number,
32
+ showIcon: PropTypes.bool,
33
+ active: PropTypes.bool,
34
+ children: PropTypes.any,
35
+ };
36
+
37
+ CollapseToggle.defaultProps = {
38
+ className: '',
39
+ iconSpacing: 20,
40
+ showIcon: true,
41
+ active: true,
42
+ };
app/assets/js/molecules/collapse.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react';
2
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
3
+
4
+ import { CollapseContext } from './collapse-context';
5
+
6
+ import CollapseToggle from './collapse-toggle';
7
+ import CollapseContent from './collapse-content';
8
+
9
+ import './collapse.scss';
10
+
11
+ export default function Collapse( props ) {
12
+ const [ isOpened, setIsOpened ] = useState( props.isOpened ),
13
+ classNameBase = 'e-app-collapse',
14
+ classes = [ classNameBase, props.className, { [ classNameBase + '--opened' ]: isOpened } ],
15
+ toggle = () => setIsOpened( ( prevState ) => ! prevState );
16
+
17
+ useEffect( () => {
18
+ if ( props.isOpened !== isOpened ) {
19
+ setIsOpened( props.isOpened );
20
+ }
21
+ }, [ props.isOpened ] );
22
+
23
+ useEffect( () => {
24
+ if ( props.onChange ) {
25
+ props.onChange( isOpened );
26
+ }
27
+ }, [ isOpened ] );
28
+
29
+ return (
30
+ <CollapseContext.Provider value={ { toggle } }>
31
+ <div className={ arrayToClassName( classes ) }>
32
+ { props.children }
33
+ </div>
34
+ </CollapseContext.Provider>
35
+ );
36
+ }
37
+
38
+ Collapse.propTypes = {
39
+ className: PropTypes.string,
40
+ isOpened: PropTypes.bool,
41
+ onChange: PropTypes.func,
42
+ children: PropTypes.oneOfType( [
43
+ PropTypes.node,
44
+ PropTypes.arrayOf( PropTypes.node ),
45
+ ] ),
46
+ };
47
+
48
+ Collapse.defaultProps = {
49
+ className: '',
50
+ isOpened: false,
51
+ };
52
+
53
+ Collapse.Toggle = CollapseToggle;
54
+ Collapse.Content = CollapseContent;
app/assets/js/molecules/collapse.scss ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-collapse-icon-color: tints(600);
2
+ $e-app-collapse-icon-dark-color: dark-tints(200);
3
+
4
+ $root: e-app-collapse;
5
+
6
+ .#{$root} {
7
+ --e-app-collapse-icon-color: #{$e-app-collapse-icon-color};
8
+
9
+ &-toggle {
10
+ position: relative;
11
+
12
+ &--active {
13
+ cursor: pointer;
14
+ }
15
+
16
+ &__icon {
17
+ color: var(--e-app-collapse-icon-color);
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ font-size: type(size, "14");
22
+ position: absolute;
23
+ top: 50%;
24
+ right: var(--e-app-collapse-toggle-icon-spacing);
25
+ transform: translateY(-50%);
26
+
27
+ &:before {
28
+ transition: all 0.2s linear;
29
+ }
30
+ }
31
+ }
32
+
33
+ &-content {
34
+ display: none;
35
+ }
36
+
37
+ &--opened {
38
+ .#{$root}-toggle {
39
+ &__icon {
40
+ &:before {
41
+ transform: rotate(-180deg);
42
+ }
43
+ }
44
+ }
45
+
46
+ .#{$root}-content {
47
+ display: block;
48
+ }
49
+ }
50
+ }
51
+
52
+ [dir="rtl"] {
53
+ .#{$root} {
54
+ &-toggle {
55
+ &__icon {
56
+ right: initial;
57
+ left: var(--e-app-collapse-toggle-icon-spacing);
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ .eps-theme-dark {
64
+ .#{$root} {
65
+ --e-app-collapse-icon-color: #{$e-app-collapse-icon-dark-color};
66
+ }
67
+ }
app/assets/js/molecules/dashboard-button.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Button from 'elementor-app/ui/molecules/button';
2
+
3
+ import useAction from 'elementor-app/hooks/use-action';
4
+
5
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
6
+
7
+ export default function DashboardButton( props ) {
8
+ const action = useAction(),
9
+ baseClassName = 'e-app-dashboard-button',
10
+ classes = [ baseClassName, props.className ];
11
+
12
+ return (
13
+ <Button
14
+ { ...props }
15
+ className={ arrayToClassName( classes ) }
16
+ text={ props.text }
17
+ onClick={ action.backToDashboard }
18
+ />
19
+ );
20
+ }
21
+
22
+ DashboardButton.propTypes = {
23
+ className: PropTypes.string,
24
+ text: PropTypes.string,
25
+ };
26
+
27
+ DashboardButton.defaultProps = {
28
+ className: '',
29
+ variant: 'contained',
30
+ color: 'primary',
31
+ text: __( 'Back to dashboard', 'elementor' ),
32
+ };
app/assets/js/molecules/data-table.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from '../utils/utils';
2
+
3
+ import Table from 'elementor-app/ui/table/table';
4
+
5
+ export default function DataTable( {
6
+ className,
7
+ onSelect,
8
+ initialSelected,
9
+ initialDisabled,
10
+ headers,
11
+ layout,
12
+ rows,
13
+ selection,
14
+ } ) {
15
+ return (
16
+ <Table
17
+ selection={ selection }
18
+ onSelect={ onSelect }
19
+ initialSelected={ initialSelected }
20
+ initialDisabled={ initialDisabled }
21
+ className={ arrayToClassName( [ 'e-app-data-table', className ] ) }
22
+ >
23
+ {
24
+ ! ! headers.length &&
25
+ <Table.Head>
26
+ <Table.Row>
27
+ {
28
+ selection &&
29
+ <Table.Cell tag="th">
30
+ <Table.Checkbox allSelectedCount={ rows.length } />
31
+ </Table.Cell>
32
+ }
33
+
34
+ {
35
+ headers.map( ( header, index ) => (
36
+ <Table.Cell tag="th" colSpan={ layout && layout[ index ] } key={ index }>
37
+ { header }
38
+ </Table.Cell>
39
+ ) )
40
+ }
41
+ </Table.Row>
42
+ </Table.Head>
43
+ }
44
+
45
+ <Table.Body>
46
+ {
47
+ rows.map( ( row, rowIndex ) => (
48
+ <Table.Row key={ rowIndex }>
49
+ {
50
+ selection &&
51
+ <Table.Cell tag="td">
52
+ <Table.Checkbox index={ rowIndex } />
53
+ </Table.Cell>
54
+ }
55
+
56
+ {
57
+ row.map( ( cell, cellIndex ) => (
58
+ <Table.Cell tag="td" colSpan={ layout && layout[ cellIndex ] } key={ cellIndex }>
59
+ { cell }
60
+ </Table.Cell>
61
+ ) )
62
+ }
63
+ </Table.Row>
64
+ ) )
65
+ }
66
+ </Table.Body>
67
+ </Table>
68
+ );
69
+ }
70
+
71
+ DataTable.propTypes = {
72
+ className: PropTypes.string,
73
+ headers: PropTypes.array,
74
+ rows: PropTypes.array,
75
+ initialDisabled: PropTypes.array,
76
+ initialSelected: PropTypes.array,
77
+ layout: PropTypes.array,
78
+ onSelect: PropTypes.func,
79
+ selection: PropTypes.bool,
80
+ withHeader: PropTypes.bool,
81
+ };
82
+
83
+ DataTable.defaultProps = {
84
+ className: '',
85
+ headers: [],
86
+ rows: [],
87
+ initialDisabled: [],
88
+ initialSelected: [],
89
+ selection: false,
90
+ };
91
+
app/assets/js/molecules/elementor-loading.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function ElementorLoading( props ) {
2
+ return (
3
+ <div className="elementor-loading">
4
+ <div className="elementor-loader-wrapper">
5
+ <div className="elementor-loader">
6
+ <div className="elementor-loader-boxes">
7
+ <div className="elementor-loader-box" />
8
+ <div className="elementor-loader-box" />
9
+ <div className="elementor-loader-box" />
10
+ <div className="elementor-loader-box" />
11
+ </div>
12
+ </div>
13
+ <div className="elementor-loading-title">{ props.loadingText }</div>
14
+ </div>
15
+ </div>
16
+ );
17
+ }
18
+
19
+ ElementorLoading.propTypes = {
20
+ loadingText: PropTypes.string,
21
+ };
22
+
23
+ ElementorLoading.defaultProps = {
24
+ loadingText: __( 'Loading', 'elementor' ),
25
+ };
app/assets/js/molecules/go-pro-button.js ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Button from 'elementor-app/ui/molecules/button';
2
+
3
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
4
+
5
+ export default function GoProButton( props ) {
6
+ const baseClassName = 'e-app-go-pro-button',
7
+ classes = [ baseClassName, props.className ];
8
+
9
+ return (
10
+ <Button
11
+ { ...props }
12
+ className={ arrayToClassName( classes ) }
13
+ text={ props.text }
14
+ />
15
+ );
16
+ }
17
+
18
+ GoProButton.propTypes = {
19
+ className: PropTypes.string,
20
+ text: PropTypes.string,
21
+ };
22
+
23
+ GoProButton.defaultProps = {
24
+ className: '',
25
+ variant: 'outlined',
26
+ size: 'sm',
27
+ color: 'cta',
28
+ target: '_blank',
29
+ rel: 'noopener noreferrer',
30
+ text: __( 'Go Pro', 'elementor' ),
31
+ };
app/assets/js/molecules/tooltip.js ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { arrayToClassName } from '../utils/utils';
3
+
4
+ export default function Tooltip( props ) {
5
+ const baseClassName = 'e-app-tooltip',
6
+ classes = [ baseClassName, props.className ],
7
+ childRef = useRef( null ),
8
+ isAborted = useRef( false ),
9
+ isManualControl = Object.prototype.hasOwnProperty.call( props, 'show' ),
10
+ [ isLibraryLoaded, setIsLibraryLoaded ] = useState( false ),
11
+ [ showTooltip, setShowTooltip ] = useState( false ),
12
+ directionsMap = {
13
+ top: 's',
14
+ right: 'w',
15
+ down: 'n',
16
+ left: 'e',
17
+ },
18
+ tipsyConfig = {
19
+ trigger: isManualControl ? 'manual' : 'hover',
20
+ gravity: directionsMap[ props.direction ],
21
+ offset: props.offset,
22
+ title() {
23
+ return props.title;
24
+ },
25
+ },
26
+ setTipsy = () => {
27
+ const $tooltipContainer = jQuery( childRef.current );
28
+
29
+ $tooltipContainer.tipsy( tipsyConfig );
30
+
31
+ if ( isManualControl ) {
32
+ const displayMode = showTooltip ? 'show' : 'hide';
33
+
34
+ $tooltipContainer.tipsy( displayMode );
35
+ }
36
+ };
37
+
38
+ useEffect( () => {
39
+ // In case that the component is disabled the tipsy library will not be loaded by default.
40
+ if ( ! props.disabled ) {
41
+ isAborted.current = false;
42
+
43
+ import(
44
+ /* webpackIgnore: true */
45
+ `${ elementorCommon.config.urls.assets }lib/tipsy/tipsy.min.js?ver=1.0.0`
46
+ ).then( () => {
47
+ if ( ! isAborted.current ) {
48
+ if ( isLibraryLoaded ) {
49
+ setTipsy();
50
+ } else {
51
+ setIsLibraryLoaded( true );
52
+ }
53
+ }
54
+ } );
55
+ }
56
+
57
+ return () => {
58
+ if ( ! props.disabled ) {
59
+ // Aborting the current dynamic-import state update in case of re-render.
60
+ isAborted.current = true;
61
+
62
+ // Cleanup of existing tipsy element in case of re-render.
63
+ const nodes = document.querySelectorAll( '.tipsy' );
64
+ nodes[ nodes.length - 1 ].remove();
65
+ }
66
+ };
67
+ }, [ props.disabled ] );
68
+
69
+ useEffect( () => {
70
+ if ( isLibraryLoaded ) {
71
+ setTipsy();
72
+ }
73
+ }, [ isLibraryLoaded, showTooltip ] );
74
+
75
+ useEffect( () => {
76
+ // The "show" state should not be changed while the component is disabled.
77
+ if ( ! props.disabled && props.show !== showTooltip ) {
78
+ setShowTooltip( props.show );
79
+ }
80
+ }, [ props.show ] );
81
+
82
+ return (
83
+ <props.tag className={ arrayToClassName( classes ) } ref={ childRef }>
84
+ { props.children }
85
+ </props.tag>
86
+ );
87
+ }
88
+
89
+ Tooltip.propTypes = {
90
+ className: PropTypes.string,
91
+ offset: PropTypes.number,
92
+ show: PropTypes.bool,
93
+ direction: PropTypes.oneOf( [ 'top', 'right', 'left', 'down' ] ),
94
+ tag: PropTypes.string.isRequired,
95
+ title: PropTypes.string.isRequired,
96
+ disabled: PropTypes.bool,
97
+ children: PropTypes.any,
98
+ };
99
+
100
+ Tooltip.defaultProps = {
101
+ className: '',
102
+ offset: 10,
103
+ direction: 'top',
104
+ disabled: false,
105
+ };
app/assets/js/molecules/upload-file.js ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useRef } from 'react';
2
+
3
+ import Button from 'elementor-app/ui/molecules/button';
4
+
5
+ import { arrayToClassName, isOneOf } from 'elementor-app/utils/utils.js';
6
+
7
+ import './upload-file.scss';
8
+
9
+ export default function UploadFile( props ) {
10
+ const fileInput = useRef( null ),
11
+ baseClassName = 'e-app-upload-file',
12
+ classes = [ baseClassName, props.className ];
13
+
14
+ // For 'wp-media' type.
15
+ let frame;
16
+
17
+ return (
18
+ <div className={ arrayToClassName( classes ) }>
19
+ <input
20
+ ref={ fileInput }
21
+ type="file"
22
+ accept={ props.filetypes.map( ( type ) => '.' + type ).join( ', ' ) }
23
+ className="e-app-upload-file__input"
24
+ onChange={ ( event ) => {
25
+ const file = event.target.files[ 0 ];
26
+
27
+ if ( file && isOneOf( file.type, props.filetypes ) ) {
28
+ props.onFileSelect( file, event, 'browse' );
29
+ } else {
30
+ fileInput.current.value = '';
31
+
32
+ props.onError( {
33
+ id: 'file_not_allowed',
34
+ message: __( 'This file type is not allowed', 'elementor' ),
35
+ } );
36
+ }
37
+ } }
38
+ />
39
+
40
+ <Button
41
+ className="e-app-upload-file__button"
42
+ text={ props.text }
43
+ variant={ props.variant }
44
+ color={ props.color }
45
+ size="lg"
46
+ hideText={ props.isLoading }
47
+ icon={ props.isLoading ? 'eicon-loading eicon-animation-spin' : '' }
48
+ onClick={ () => {
49
+ if ( props.onFileChoose ) {
50
+ props.onFileChoose();
51
+ }
52
+ if ( ! props.isLoading ) {
53
+ if ( props.onButtonClick ) {
54
+ props.onButtonClick();
55
+ }
56
+
57
+ if ( 'file-explorer' === props.type ) {
58
+ fileInput.current.click();
59
+ } else if ( 'wp-media' === props.type ) {
60
+ if ( frame ) {
61
+ frame.open();
62
+ return;
63
+ }
64
+
65
+ // Initialize the WP Media frame.
66
+ frame = wp.media( {
67
+ multiple: false,
68
+ library: {
69
+ type: [ 'image', 'image/svg+xml' ],
70
+ },
71
+ } );
72
+
73
+ frame.on( 'select', () => {
74
+ if ( props.onWpMediaSelect ) {
75
+ props.onWpMediaSelect( frame );
76
+ }
77
+ } );
78
+
79
+ frame.open();
80
+ }
81
+ }
82
+ } }
83
+ />
84
+ </div>
85
+ );
86
+ }
87
+
88
+ UploadFile.propTypes = {
89
+ className: PropTypes.string,
90
+ type: PropTypes.string,
91
+ onWpMediaSelect: PropTypes.func,
92
+ text: PropTypes.string,
93
+ onFileSelect: PropTypes.func,
94
+ isLoading: PropTypes.bool,
95
+ filetypes: PropTypes.array.isRequired,
96
+ onError: PropTypes.func,
97
+ variant: PropTypes.string,
98
+ color: PropTypes.string,
99
+ onButtonClick: PropTypes.func,
100
+ onFileChoose: PropTypes.func,
101
+ };
102
+
103
+ UploadFile.defaultProps = {
104
+ className: '',
105
+ type: 'file-explorer',
106
+ text: __( 'Select File', 'elementor' ),
107
+ onError: () => {},
108
+ variant: 'contained',
109
+ color: 'primary',
110
+ };
app/assets/js/molecules/upload-file.scss ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ .e-app-upload-file {
2
+ &__input {
3
+ display: none;
4
+ }
5
+ }
app/assets/js/organisms/drop-zone.js ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName, isOneOf } from 'elementor-app/utils/utils.js';
2
+
3
+ import UploadFile from 'elementor-app/molecules/upload-file';
4
+ import DragDrop from 'elementor-app/ui/atoms/drag-drop';
5
+ import Icon from 'elementor-app/ui/atoms/icon';
6
+ import Heading from 'elementor-app/ui/atoms/heading';
7
+ import Text from 'elementor-app/ui/atoms/text';
8
+
9
+ import './drop-zone.scss';
10
+
11
+ export default function DropZone( props ) {
12
+ const classes = [ 'e-app-drop-zone', props.className ],
13
+ dragDropEvents = {
14
+ onDrop: ( event ) => {
15
+ if ( ! props.isLoading ) {
16
+ const file = event.dataTransfer.files[ 0 ];
17
+
18
+ if ( file && isOneOf( file.type, props.filetypes ) ) {
19
+ props.onFileSelect( file, event, 'drop' );
20
+ } else {
21
+ props.onError( {
22
+ id: 'file_not_allowed',
23
+ message: __( 'This file type is not allowed', 'elementor' ),
24
+ } );
25
+ }
26
+ }
27
+ },
28
+ };
29
+
30
+ return (
31
+ <section className={ arrayToClassName( classes ) }>
32
+ <DragDrop { ...dragDropEvents } isLoading={ props.isLoading }>
33
+ { props.icon && <Icon className={ `e-app-drop-zone__icon ${ props.icon }` } /> }
34
+
35
+ { props.heading && <Heading variant="display-3">{ props.heading }</Heading> }
36
+
37
+ { props.text && <Text variant="xl" className="e-app-drop-zone__text">{ props.text }</Text> }
38
+
39
+ { props.secondaryText && <Text variant="xl" className="e-app-drop-zone__secondary-text">{ props.secondaryText }</Text> }
40
+
41
+ { props.showButton &&
42
+ <UploadFile
43
+ isLoading={ props.isLoading }
44
+ type={ props.type }
45
+ onButtonClick={ props.onButtonClick }
46
+ onFileSelect={ props.onFileSelect }
47
+ onWpMediaSelect={ ( frame ) => props.onWpMediaSelect( frame ) }
48
+ onError={ ( error ) => props.onError( error ) }
49
+ text={ props.buttonText }
50
+ filetypes={ props.filetypes }
51
+ variant={ props.buttonVariant }
52
+ color={ props.buttonColor }
53
+ onFileChoose={ props.onFileChoose }
54
+ /> }
55
+
56
+ { props.description && <Text variant="xl" className="e-app-drop-zone__description">{ props.description }</Text> }
57
+ </DragDrop>
58
+ </section>
59
+ );
60
+ }
61
+
62
+ DropZone.propTypes = {
63
+ className: PropTypes.string,
64
+ children: PropTypes.any,
65
+ type: PropTypes.string,
66
+ onFileSelect: PropTypes.func.isRequired,
67
+ onWpMediaSelect: PropTypes.func,
68
+ heading: PropTypes.string,
69
+ text: PropTypes.string,
70
+ secondaryText: PropTypes.string,
71
+ buttonText: PropTypes.string,
72
+ buttonVariant: PropTypes.string,
73
+ buttonColor: PropTypes.string,
74
+ icon: PropTypes.string,
75
+ showButton: PropTypes.bool,
76
+ showIcon: PropTypes.bool,
77
+ isLoading: PropTypes.bool,
78
+ filetypes: PropTypes.array.isRequired,
79
+ onError: PropTypes.func,
80
+ description: PropTypes.string,
81
+ onButtonClick: PropTypes.func,
82
+ onFileChoose: PropTypes.func,
83
+ };
84
+
85
+ DropZone.defaultProps = {
86
+ className: '',
87
+ type: 'file-explorer',
88
+ icon: 'eicon-library-upload',
89
+ showButton: true,
90
+ showIcon: true,
91
+ onError: () => {},
92
+ };
app/assets/js/organisms/drop-zone.scss ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-drop-zone-icon-color: tints(400);
2
+ $e-app-drop-zone-icon-dark-color: tints(400);
3
+ $e-app-drop-zone-text-color: tints(500);
4
+ $e-app-drop-zone-text-dark-color: dark-tints(200);
5
+ $e-app-drop-zone-secondary-text-color: tints(600);
6
+ $e-app-drop-zone-secondary-text-dark-color: dark-tints(100);
7
+
8
+ $root: e-app-drop-zone;
9
+
10
+ .#{$root} {
11
+ --e-app-drop-zone-icon-color: #{$e-app-drop-zone-icon-color};
12
+ --e-app-drop-zone-text-color: #{$e-app-drop-zone-text-color};
13
+ --e-app-drop-zone-secondary-text-color: #{$e-app-drop-zone-secondary-text-color};
14
+
15
+ &__icon {
16
+ margin-bottom: spacing(44);
17
+ color: var(--e-app-drop-zone-icon-color);
18
+ font-size: 60px;
19
+ }
20
+
21
+ &__text {
22
+ color: var(--e-app-drop-zone-text-color);
23
+ }
24
+
25
+ &__secondary-text {
26
+ color: var(--e-app-drop-zone-secondary-text-color);
27
+ }
28
+ }
29
+
30
+ .eps-theme-dark {
31
+ .#{$root} {
32
+ --e-app-drop-zone-icon-color: #{$e-app-drop-zone-icon-dark-color};
33
+ --e-app-drop-zone-text-color: #{$e-app-drop-zone-text-dark-color};
34
+ --e-app-drop-zone-secondary-text-color: #{$e-app-drop-zone-secondary-text-dark-color};
35
+ }
36
+ }
app/assets/js/organisms/error-boundary.js ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Dialog from 'elementor-app/ui/dialog/dialog';
2
+
3
+ // In the current time there is no solution to use "getDerivedStateFromError" static method with functional component
4
+ // That is why this component is a class component.
5
+ // @link https://reactjs.org/docs/hooks-faq.html#do-hooks-cover-all-use-cases-for-classes
6
+ export default class ErrorBoundary extends React.Component {
7
+ static propTypes = {
8
+ children: PropTypes.any,
9
+ title: PropTypes.string,
10
+ text: PropTypes.string,
11
+ learnMoreUrl: PropTypes.string,
12
+ };
13
+
14
+ static defaultProps = {
15
+ title: __( 'App could not be loaded', 'elementor' ),
16
+ text: __( 'We’re sorry, but something went wrong. Click on ‘Learn more’ and follow each of the steps to quickly solve it.', 'elementor' ),
17
+ learnMoreUrl: 'https://go.elementor.com/app-general-load-issue/',
18
+ };
19
+
20
+ constructor( props ) {
21
+ super( props );
22
+
23
+ this.state = {
24
+ hasError: null,
25
+ };
26
+ }
27
+
28
+ static getDerivedStateFromError() {
29
+ return { hasError: true };
30
+ }
31
+
32
+ goBack() {
33
+ // If the app was opened inside an iframe, it will close it,
34
+ // if not, it will redirect to the last location.
35
+ if ( window.top !== window.self ) {
36
+ window.top.$e.run( 'app/close' );
37
+ }
38
+
39
+ window.location = elementorAppConfig.return_url;
40
+ }
41
+
42
+ render() {
43
+ if ( this.state.hasError ) {
44
+ return <Dialog
45
+ title={ this.props.title }
46
+ text={ this.props.text }
47
+ approveButtonUrl={ this.props.learnMoreUrl }
48
+ approveButtonColor="link"
49
+ approveButtonTarget="_blank"
50
+ approveButtonText={ __( 'Learn More', 'elementor' ) }
51
+ dismissButtonText={ __( 'Go Back', 'elementor' ) }
52
+ dismissButtonOnClick={ this.goBack }
53
+ />;
54
+ }
55
+
56
+ return this.props.children;
57
+ }
58
+ }
app/assets/js/organisms/unfiltered-files-dialog.js ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react';
2
+
3
+ import Dialog from 'elementor-app/ui/dialog/dialog';
4
+
5
+ import useAjax from 'elementor-app/hooks/use-ajax';
6
+
7
+ export default function UnfilteredFilesDialog( props ) {
8
+ const { show, setShow, onReady, onCancel, onDismiss, onLoad, onEnable, onClose } = props,
9
+ { ajaxState, setAjax } = useAjax(),
10
+ [ enableUnfilteredFiles, setEnableUnfilteredFiles ] = useState( false ),
11
+ [ isEnableError, setIsEnableError ] = useState( false );
12
+
13
+ // Sending the enable unfiltered files request.
14
+ useEffect( () => {
15
+ if ( enableUnfilteredFiles ) {
16
+ setShow( false );
17
+
18
+ setAjax( {
19
+ data: {
20
+ action: 'elementor_ajax',
21
+ actions: JSON.stringify( {
22
+ enable_unfiltered_files_upload: {
23
+ action: 'enable_unfiltered_files_upload',
24
+ },
25
+ } ),
26
+ },
27
+ } );
28
+ if ( onEnable ) {
29
+ onEnable();
30
+ }
31
+ }
32
+ }, [ enableUnfilteredFiles ] );
33
+
34
+ // Enabling unfiltered files ajax status.
35
+ useEffect( () => {
36
+ switch ( ajaxState.status ) {
37
+ case 'success':
38
+ onReady();
39
+ break;
40
+ case 'error':
41
+ setIsEnableError( true );
42
+ setShow( true );
43
+ break;
44
+ }
45
+ }, [ ajaxState ] );
46
+
47
+ useEffect( () => {
48
+ if ( show && onLoad ) {
49
+ onLoad();
50
+ }
51
+ }, [ show ] );
52
+
53
+ if ( ! show ) {
54
+ return null;
55
+ }
56
+
57
+ return (
58
+ <>
59
+ {
60
+ isEnableError
61
+ ? <Dialog
62
+ title={ __( 'Something went wrong.', 'elementor' ) }
63
+ text={ props.errorModalText }
64
+ approveButtonColor="link"
65
+ approveButtonText={ __( 'Continue', 'elementor' ) }
66
+ approveButtonOnClick={ onReady }
67
+ dismissButtonText={ __( 'Go Back', 'elementor' ) }
68
+ dismissButtonOnClick={ onCancel }
69
+ onClose={ onCancel }
70
+ />
71
+ : <Dialog
72
+ title={ __( 'First, enable unfiltered file uploads.', 'elementor' ) }
73
+ text={ props.confirmModalText }
74
+ approveButtonColor="link"
75
+ approveButtonText={ __( 'Enable', 'elementor' ) }
76
+ approveButtonOnClick={ () => setEnableUnfilteredFiles( true ) }
77
+ dismissButtonText={ __( 'Skip', 'elementor' ) }
78
+ dismissButtonOnClick={ onDismiss || onReady }
79
+ onClose={ onClose || onDismiss || onReady }
80
+ />
81
+ }
82
+ </>
83
+ );
84
+ }
85
+
86
+ UnfilteredFilesDialog.propTypes = {
87
+ show: PropTypes.bool,
88
+ setShow: PropTypes.func.isRequired,
89
+ onReady: PropTypes.func.isRequired,
90
+ onCancel: PropTypes.func.isRequired,
91
+ onDismiss: PropTypes.func,
92
+ confirmModalText: PropTypes.string.isRequired,
93
+ errorModalText: PropTypes.string.isRequired,
94
+ onLoad: PropTypes.func,
95
+ onEnable: PropTypes.func,
96
+ onClose: PropTypes.func,
97
+ };
98
+
99
+ UnfilteredFilesDialog.defaultProps = {
100
+ show: false,
101
+ onReady: () => {},
102
+ onCancel: () => {},
103
+ };
app/assets/js/organisms/wizard-footer.js ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import Grid from 'elementor-app/ui/grid/grid';
4
+
5
+ import './wizard-footer.scss';
6
+
7
+ export default function WizardFooter( props ) {
8
+ const baseClassName = 'e-app-wizard-footer',
9
+ classes = [ baseClassName, props.className ];
10
+
11
+ if ( props.separator ) {
12
+ classes.push( baseClassName + '__separator' );
13
+ }
14
+
15
+ return (
16
+ <Grid container { ...props } className={ arrayToClassName( classes ) }>
17
+ { props.children }
18
+ </Grid>
19
+ );
20
+ }
21
+
22
+ WizardFooter.propTypes = {
23
+ className: PropTypes.string,
24
+ justify: PropTypes.any,
25
+ separator: PropTypes.any,
26
+ children: PropTypes.oneOfType( [
27
+ PropTypes.string,
28
+ PropTypes.object,
29
+ PropTypes.arrayOf( PropTypes.object ),
30
+ ] ).isRequired,
31
+ };
32
+
33
+ WizardFooter.defaultProps = {
34
+ className: '',
35
+ };
app/assets/js/organisms/wizard-footer.scss ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-wizard-footer-border-color: tints(300);
2
+ $e-app-wizard-footer-dark-border-color: dark-tints(500);
3
+
4
+ $root: e-app-wizard-footer;
5
+
6
+ .#{$root} {
7
+ --e-app-wizard-footer-border-color: #{$e-app-wizard-footer-border-color};
8
+
9
+ padding: spacing(8);
10
+
11
+ &__separator {
12
+ border-top: 1px solid var(--e-app-wizard-footer-border-color);
13
+ }
14
+ }
15
+
16
+ .eps-theme-dark {
17
+ .#{$root} {
18
+ --e-app-wizard-footer-border-color: #{$e-app-wizard-footer-dark-border-color};
19
+ }
20
+ }
app/assets/js/package.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Alphabetical order.
2
+ // App UI
3
+ import AddNewButton from './ui/molecules/add-new-button';
4
+ import Box from './ui/atoms/box';
5
+ import Button from './ui/molecules/button';
6
+ import Card from './ui/card/card';
7
+ import CardBody from './ui/card/card-body';
8
+ import CardFooter from './ui/card/card-footer';
9
+ import CardImage from './ui/card/card-image';
10
+ import CardHeader from './ui/card/card-header';
11
+ import CardOverlay from './ui/card/card-overlay';
12
+ import Checkbox from './ui/atoms/checkbox';
13
+ import Collapse from './molecules/collapse';
14
+ import CssGrid from './ui/atoms/css-grid';
15
+ import Dialog from './ui/dialog/dialog';
16
+ import DragDrop from './ui/atoms/drag-drop';
17
+ import DropZone from './organisms/drop-zone';
18
+ import ErrorBoundary from './organisms/error-boundary';
19
+ import Heading from './ui/atoms/heading';
20
+ import Grid from './ui/grid/grid';
21
+ import Icon from './ui/atoms/icon';
22
+ import List from './ui/molecules/list';
23
+ import Menu from './ui/menu/menu';
24
+ import MenuItem from './ui/menu/menu-item';
25
+ import { Modal, default as ModalProvider } from './ui/modal/modal';
26
+ import NotFound from './pages/not-found';
27
+ import Notice from './ui/molecules/notice';
28
+ import Page from './layout/page';
29
+ import Popover from './ui/molecules/popover';
30
+ import Select from './ui/atoms/select';
31
+ import Select2 from './ui/molecules/select2';
32
+ import Text from './ui/atoms/text';
33
+ import UploadFile from './molecules/upload-file';
34
+ import InlineLink from './ui/molecules/inline-link';
35
+
36
+ // Components
37
+ import UnfilteredFilesDialog from './organisms/unfiltered-files-dialog.js';
38
+
39
+ // Hooks
40
+ import useAjax from './hooks/use-ajax';
41
+ import useAction from './hooks/use-action';
42
+ import usePageTitle from './hooks/use-page-title';
43
+ import useQueryParams from './hooks/use-query-params';
44
+
45
+ export const appUi = {
46
+ AddNewButton,
47
+ Box,
48
+ Button,
49
+ Card,
50
+ CardBody,
51
+ CardFooter,
52
+ CardHeader,
53
+ CardImage,
54
+ CardOverlay,
55
+ Checkbox,
56
+ Collapse,
57
+ CssGrid,
58
+ Dialog,
59
+ DragDrop,
60
+ DropZone,
61
+ ErrorBoundary,
62
+ Heading,
63
+ Grid,
64
+ Icon,
65
+ List,
66
+ Menu,
67
+ MenuItem,
68
+ Modal,
69
+ ModalProvider,
70
+ NotFound,
71
+ Notice,
72
+ Page,
73
+ Popover,
74
+ Select,
75
+ Select2,
76
+ Text,
77
+ UploadFile,
78
+ InlineLink,
79
+ };
80
+
81
+ export const components = {
82
+ UnfilteredFilesDialog,
83
+ };
84
+
85
+ export const hooks = {
86
+ useAjax,
87
+ useAction,
88
+ usePageTitle,
89
+ useQueryParams,
90
+ };
app/assets/js/pages/index.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Redirect } from '@reach/router';
2
+ import actionsMap from 'elementor-app/url-actions/actions-map';
3
+
4
+ export default function Index() {
5
+ const urlSearchParams = new URLSearchParams( window.location.search ),
6
+ queryParams = Object.fromEntries( urlSearchParams.entries() ),
7
+ // The 'action' query param is translated into a route URL.
8
+ url = actionsMap[ queryParams.action ] || elementorAppConfig.menu_url.split( '#' )?.[ 1 ];
9
+
10
+ return (
11
+ <Redirect to={ url || '/not-found' } noThrow={ true } />
12
+ );
13
+ }
app/assets/js/pages/not-found.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Page from 'elementor-app/layout/page';
2
+
3
+ export default function NotFound() {
4
+ const config = {
5
+ title: __( 'Not Found', 'elementor' ),
6
+ className: 'eps-app__not-found',
7
+ content: <h1> { __( 'Not Found', 'elementor' ) } </h1>,
8
+ sidebar: <></>,
9
+ };
10
+
11
+ return (
12
+ <Page { ...config } />
13
+ );
14
+ }
app/assets/js/router.js ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * App Router
3
+ *
4
+ * TODO: Temporary solution for routing extensibility.
5
+ */
6
+
7
+ class Router {
8
+ /**
9
+ * @type {*[]}
10
+ */
11
+ routes = [];
12
+
13
+ history = null;
14
+
15
+ /**
16
+ *
17
+ * @param {{path: string, component: Object, props: Object}} route
18
+ */
19
+ addRoute( route ) {
20
+ this.routes.push( route );
21
+ }
22
+
23
+ getRoutes() {
24
+ return this.routes.map( ( route ) => {
25
+ const props = route.props || {};
26
+ // Use the path as a key, and add it as a prop.
27
+ props.path = props.key = route.path;
28
+ return React.createElement( route.component, props );
29
+ } );
30
+ }
31
+ }
32
+
33
+ const router = new Router();
34
+
35
+ // Make router available for use within packages.
36
+ window.elementorAppPackages = {
37
+ router,
38
+ };
39
+
40
+ export default router;
app/assets/js/ui/atoms/box.js ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName, pxToRem } from 'elementor-app/utils/utils.js';
2
+
3
+ import './box.scss';
4
+
5
+ export default function Box( props ) {
6
+ const baseClassName = 'eps-box',
7
+ classes = [ baseClassName, props.className ],
8
+ style = {};
9
+
10
+ if ( Object.prototype.hasOwnProperty.call( props, 'padding' ) ) {
11
+ style[ '--eps-box-padding' ] = pxToRem( props.padding );
12
+
13
+ classes.push( baseClassName + '--padding' );
14
+ }
15
+
16
+ return (
17
+ <div style={ style } className={ arrayToClassName( classes ) }>
18
+ { props.children }
19
+ </div>
20
+ );
21
+ }
22
+
23
+ Box.propTypes = {
24
+ className: PropTypes.string,
25
+ padding: PropTypes.string,
26
+ children: PropTypes.oneOfType( [
27
+ PropTypes.string,
28
+ PropTypes.object,
29
+ PropTypes.arrayOf( PropTypes.object ),
30
+ ] ).isRequired,
31
+ };
32
+
33
+ Box.defaultProps = {
34
+ className: '',
35
+ };
app/assets/js/ui/atoms/box.scss ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-box-background-color: theme-colors(light);
2
+ $eps-box-dark-background-color: dark-tints(600);
3
+ $eps-box-color: tints(800);
4
+ $eps-box-dark-color: dark-tints(100);
5
+ $eps-box-input-color: tints(800);
6
+ $eps-box-input-dark-color: dark-tints(100);
7
+
8
+ $root: eps-box;
9
+
10
+ .#{$root} {
11
+ --eps-box-background-color: #{$eps-box-background-color};
12
+ --eps-box-color: #{$eps-box-color};
13
+ --eps-box-input-color: #{$eps-box-input-color};
14
+
15
+ padding: 0;
16
+ border-radius: $eps-radius;
17
+ background-color: var(--eps-box-background-color);
18
+ color: var(--eps-box-color);
19
+
20
+ &--padding {
21
+ padding: var(--eps-box-padding);
22
+ }
23
+
24
+ > input {
25
+ width: 100%;
26
+ outline: 0;
27
+ border: 0;
28
+ background-color: var(--eps-box-background-color);
29
+ color: var(--eps-box-input-color);
30
+ }
31
+ }
32
+
33
+ .eps-theme-dark {
34
+ .#{$root} {
35
+ --eps-box-background-color: #{$eps-box-dark-background-color};
36
+ --eps-box-color: #{$eps-box-dark-color};
37
+ --eps-box-input-color: #{$eps-box-input-dark-color};
38
+ }
39
+ }
app/assets/js/ui/atoms/checkbox-api.scss ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // CHECKBOX: API
2
+ $eps-checkbox-size: 15px;
3
+ $eps-checkbox-border-color: tints(300);
4
+ $eps-checkbox-hover-border-color: darken(tints(300), $eps-hover-state-operator);
5
+ $eps-checkbox-active-border-color: lighten(tints(300), $eps-active-state-operator);
6
+ $eps-checkbox-border-radius: $eps-radius;
7
+ $eps-checkbox-background-color: theme-colors(light);
8
+ $eps-checkbox-checked-background-color: theme-colors(success);
9
+ $eps-checkbox-checked-hover-background-color: darken($eps-checkbox-checked-background-color, $eps-hover-state-operator);
10
+ $eps-checkbox-checked-active-background-color: lighten($eps-checkbox-checked-background-color, $eps-active-state-operator);
11
+ $eps-checkbox-checked-disabled-background-color: theme-colors(disabled);
12
+ $eps-checkbox-indicator-color: theme-colors(light);
13
+ $eps-checkbox-indicator-border-width: 1px;
14
+ $eps-checkbox-indicator-scale-unit: calc(1/4);
15
+ $eps-checkbox-indicator-size-unit: ceil($eps-checkbox-indicator-scale-unit * $eps-checkbox-size) - $eps-checkbox-indicator-border-width;
16
+ $eps-checkbox-indeterminate-indicator-size-unit: 7px;
17
+ $eps-checkbox-error-background-color: theme-colors(danger);
18
+
19
+ :root {
20
+ --checkbox-border-color: #{$eps-checkbox-border-color};
21
+ --checkbox-hover-border-color: #{$eps-checkbox-hover-border-color};
22
+ --checkbox-active-border-color: #{$eps-checkbox-active-border-color};
23
+ --checkbox-background-color: #{$eps-checkbox-background-color};
24
+ --checkbox-checked-background-color: #{$eps-checkbox-checked-background-color};
25
+ --checkbox-checked-hover-background-color: #{$eps-checkbox-checked-hover-background-color};
26
+ --checkbox-checked-active-background-color: #{$eps-checkbox-checked-active-background-color};
27
+ --checkbox-checked-disabled-background-color: #{$eps-checkbox-checked-disabled-background-color};
28
+ --checkbox-indicator-color: #{$eps-checkbox-indicator-color};
29
+ --checkbox-error-background-color: #{$eps-checkbox-error-background-color};
30
+ }
31
+
32
+ .eps-theme-dark {
33
+ --checkbox-background-color: transparent;
34
+ }
35
+
36
+ // Theming:
37
+ // no dark theme detected
app/assets/js/ui/atoms/checkbox.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import './checkbox.scss';
4
+
5
+ export default function Checkbox( { className, checked, rounded, indeterminate, error, disabled, onChange } ) {
6
+ const baseClassName = 'eps-checkbox',
7
+ classes = [ baseClassName, className ];
8
+
9
+ if ( rounded ) {
10
+ classes.push( baseClassName + '--rounded' );
11
+ }
12
+
13
+ if ( indeterminate ) {
14
+ classes.push( baseClassName + '--indeterminate' );
15
+ }
16
+
17
+ if ( error ) {
18
+ classes.push( baseClassName + '--error' );
19
+ }
20
+
21
+ return (
22
+ <input
23
+ className={ arrayToClassName( classes ) }
24
+ type="checkbox"
25
+ checked={ checked }
26
+ disabled={ disabled }
27
+ onChange={ onChange }
28
+ />
29
+ );
30
+ }
31
+
32
+ Checkbox.propTypes = {
33
+ className: PropTypes.string,
34
+ checked: PropTypes.bool,
35
+ disabled: PropTypes.bool,
36
+ indeterminate: PropTypes.bool,
37
+ rounded: PropTypes.bool,
38
+ error: PropTypes.bool,
39
+ onChange: PropTypes.func,
40
+ };
41
+
42
+ Checkbox.defaultProps = {
43
+ className: '',
44
+ checked: null,
45
+ disabled: false,
46
+ indeterminate: false,
47
+ error: false,
48
+ onChange: () => {},
49
+ };
app/assets/js/ui/atoms/checkbox.scss ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "checkbox-api";
2
+
3
+
4
+ // checkbox label
5
+ .#{$eps-prefix}checkbox {
6
+ -webkit-appearance: none;
7
+ border-radius: $eps-checkbox-border-radius;
8
+ width: $eps-checkbox-size;
9
+ height: $eps-checkbox-size;
10
+ outline: 0;
11
+ background-color: var(--checkbox-background-color);
12
+ display: inline-flex;
13
+ justify-content: center;
14
+ align-items: center;
15
+ border: 1px solid var(--checkbox-border-color);
16
+
17
+ &::after {
18
+ display: inline-block;
19
+ margin-bottom: calc(#{$eps-checkbox-indicator-scale-unit} / 2 * 100%);
20
+ content: " ";
21
+ width: $eps-checkbox-indicator-size-unit;
22
+ height: (2 * $eps-checkbox-indicator-size-unit);
23
+ transform: rotate(45deg);
24
+ }
25
+
26
+ &:hover {
27
+ --checkbox-border-color: var(--checkbox-hover-border-color);
28
+ }
29
+
30
+ &:active {
31
+ --checkbox-border-color: var(--checkbox-active-border-color);
32
+ }
33
+
34
+ &:checked {
35
+ --checkbox-background-color: var(--checkbox-checked-background-color);
36
+ --checkbox-border-color: var(--checkbox-checked-background-color);
37
+
38
+ &::after {
39
+ border: solid $eps-checkbox-indicator-color;
40
+ border-width: 0 $eps-checkbox-indicator-border-width $eps-checkbox-indicator-border-width 0;
41
+ }
42
+
43
+ &:hover {
44
+ --checkbox-background-color: var(--checkbox-checked-hover-background-color);
45
+ --checkbox-border-color: var(--checkbox-checked-hover-background-color);
46
+ }
47
+
48
+ &:active {
49
+ --checkbox-background-color: var(--checkbox-checked-active-background-color);
50
+ --checkbox-border-color: var(--checkbox-checked-active-background-color);
51
+ }
52
+
53
+ &:disabled {
54
+ --checkbox-background-color: var(--checkbox-checked-disabled-background-color);
55
+ --checkbox-border-color: var(--checkbox-checked-disabled-background-color);
56
+ }
57
+ }
58
+
59
+ &--rounded {
60
+ border-radius: 50%;
61
+ }
62
+
63
+ &--indeterminate {
64
+ &::after {
65
+ display: inline-block;
66
+ margin-bottom: 0;
67
+ content: " ";
68
+ width: $eps-checkbox-indeterminate-indicator-size-unit;
69
+ height: 0;
70
+ transform: rotate(0deg);
71
+ border-top: $eps-checkbox-indicator-border-width solid $eps-checkbox-indicator-color;
72
+ }
73
+
74
+ --checkbox-background-color: var(--checkbox-checked-background-color);
75
+ --checkbox-border-color: var(--checkbox-checked-background-color);
76
+ }
77
+
78
+ &--error,
79
+ &--error:checked {
80
+ &::before,
81
+ &::after {
82
+ display: inline-block;
83
+ margin-bottom: 0;
84
+ content: " ";
85
+ width: $eps-checkbox-indeterminate-indicator-size-unit;
86
+ height: 0;
87
+ border: solid $eps-checkbox-indicator-color;
88
+ border-width: $eps-checkbox-indicator-border-width 0 0 0;
89
+ position: absolute;
90
+ }
91
+
92
+ &::before {
93
+ transform: rotate(45deg);
94
+ }
95
+
96
+ &::after {
97
+ transform: rotate(-45deg);
98
+ }
99
+
100
+ &,
101
+ &:hover {
102
+ --checkbox-background-color: var(--checkbox-error-background-color);
103
+ --checkbox-border-color: var(--checkbox-error-background-color);
104
+ }
105
+ }
106
+ }
app/assets/js/ui/atoms/css-grid.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { pxToRem } from '../../utils/utils';
2
+
3
+ import './css-grid.scss';
4
+
5
+ export default function CssGrid( props ) {
6
+ const gridStyle = {
7
+ '--eps-grid-columns': props.columns,
8
+ '--eps-grid-spacing': pxToRem( props.spacing ),
9
+ '--eps-grid-col-min-width': pxToRem( props.colMinWidth ),
10
+ '--eps-grid-col-max-width': pxToRem( props.colMaxWidth ),
11
+ };
12
+
13
+ return (
14
+ <div style={ gridStyle } className={ `eps-css-grid ${ props.className }` }>
15
+ { props.children }
16
+ </div>
17
+ );
18
+ }
19
+
20
+ CssGrid.propTypes = {
21
+ className: PropTypes.string,
22
+ children: PropTypes.any.isRequired,
23
+ columns: PropTypes.number,
24
+ spacing: PropTypes.number,
25
+ colMinWidth: PropTypes.number,
26
+ colMaxWidth: PropTypes.number,
27
+ };
28
+
29
+ CssGrid.defaultProps = {
30
+ spacing: 24,
31
+ className: '',
32
+ };
app/assets/js/ui/atoms/css-grid.scss ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ $eps-grid-gap: var(--eps-grid-spacing);
2
+ $eps-grid-columns: repeat(var(--eps-grid-columns, auto-fill), minmax(var(--eps-grid-col-min-width, 1fr), var(--eps-grid-col-max-width, 1fr)));
3
+
4
+ .#{$eps-prefix}css-grid {
5
+ display: grid;
6
+ grid-template-columns: $eps-grid-columns;
7
+ grid-gap: $eps-grid-gap;
8
+ }
app/assets/js/ui/atoms/drag-drop.js ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+
3
+ import { arrayToClassName } from '../../utils/utils';
4
+
5
+ import './drag-drop.scss';
6
+
7
+ export default function DragDrop( props ) {
8
+ const [ isDragOver, setIsDragOver ] = useState( false ),
9
+ getClassName = () => {
10
+ const baseClassName = 'e-app-drag-drop',
11
+ classes = [ baseClassName, props.className ];
12
+
13
+ if ( isDragOver && ! props.isLoading ) {
14
+ classes.push( baseClassName + '--drag-over' );
15
+ }
16
+
17
+ return arrayToClassName( classes );
18
+ },
19
+ onDragDropActions = ( event ) => {
20
+ event.preventDefault();
21
+ event.stopPropagation();
22
+ },
23
+ dragDropEvents = {
24
+ onDrop: ( event ) => {
25
+ onDragDropActions( event );
26
+
27
+ setIsDragOver( false );
28
+
29
+ if ( props.onDrop ) {
30
+ props.onDrop( event );
31
+ }
32
+ },
33
+ onDragOver: ( event ) => {
34
+ onDragDropActions( event );
35
+
36
+ setIsDragOver( true );
37
+
38
+ if ( props.onDragOver ) {
39
+ props.onDragOver( event );
40
+ }
41
+ },
42
+ onDragLeave: ( event ) => {
43
+ onDragDropActions( event );
44
+
45
+ setIsDragOver( false );
46
+
47
+ if ( props.onDragLeave ) {
48
+ props.onDragLeave( event );
49
+ }
50
+ },
51
+ };
52
+
53
+ return (
54
+ <div { ...dragDropEvents } className={ getClassName() }>
55
+ { props.children }
56
+ </div>
57
+ );
58
+ }
59
+
60
+ DragDrop.propTypes = {
61
+ className: PropTypes.string,
62
+ children: PropTypes.any,
63
+ onDrop: PropTypes.func,
64
+ onDragLeave: PropTypes.func,
65
+ onDragOver: PropTypes.func,
66
+ isLoading: PropTypes.bool,
67
+ };
68
+
69
+ DragDrop.defaultProps = {
70
+ className: '',
71
+ };
app/assets/js/ui/atoms/drag-drop.scss ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-drag-drop-background-color: theme-colors(light);
2
+ $e-app-drag-drop-dark-background-color: dark-tints(600);
3
+ $e-app-drag-drop-outline-color: tints(300);
4
+ $e-app-drag-drop-dark-outline-color: dark-tints(300);
5
+
6
+ :root {
7
+ --e-app-drag-drop-background-color: #{$e-app-drag-drop-background-color};
8
+ --e-app-drag-drop-outline-color: #{$e-app-drag-drop-outline-color};
9
+ }
10
+
11
+ .eps-theme-dark {
12
+ --e-app-drag-drop-background-color: #{$e-app-drag-drop-dark-background-color};
13
+ --e-app-drag-drop-outline-color: #{$e-app-drag-drop-dark-outline-color};
14
+ }
15
+
16
+ .e-app-drag-drop {
17
+ background-color: var(--e-app-drag-drop-background-color);
18
+ outline: 2px dashed var(--e-app-drag-drop-outline-color);
19
+ outline-offset: spacing(12) * -1;
20
+ margin-bottom: spacing(24);
21
+ padding: 1.5 * spacing(44);
22
+ text-align: center;
23
+
24
+ &--drag-over {
25
+ outline-color: theme-colors(info);
26
+ }
27
+ }
app/assets/js/ui/atoms/heading.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from '../../utils/utils';
2
+
3
+ export default function Heading( props ) {
4
+ const baseClassName = 'eps',
5
+ classes = [
6
+ props.className,
7
+ ];
8
+
9
+ if ( props.variant ) {
10
+ classes.push( baseClassName + '-' + props.variant );
11
+ }
12
+
13
+ const Element = () => React.createElement( props.tag, {
14
+ className: arrayToClassName( classes ),
15
+ }, props.children );
16
+
17
+ return <Element />;
18
+ }
19
+
20
+ Heading.propTypes = {
21
+ className: PropTypes.string,
22
+ children: PropTypes.oneOfType( [
23
+ PropTypes.string,
24
+ PropTypes.object,
25
+ PropTypes.arrayOf( PropTypes.object ),
26
+ ] ).isRequired,
27
+ tag: PropTypes.oneOf( [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ] ),
28
+ variant: PropTypes.oneOf( [ 'display-1', 'display-2', 'display-3', 'display-4', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ] ).isRequired,
29
+ };
30
+
31
+ Heading.defaultProps = {
32
+ className: '',
33
+ tag: 'h1',
34
+ };
app/assets/js/ui/atoms/icon.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function Icon( props ) {
2
+ return (
3
+ <i className={ `eps-icon ${ props.className }` } />
4
+ );
5
+ }
6
+
7
+ Icon.propTypes = {
8
+ className: PropTypes.string.isRequired,
9
+ };
10
+
11
+ Icon.defaultProps = {
12
+ className: '',
13
+ };
app/assets/js/ui/atoms/select.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function Select( props ) {
2
+ return (
3
+ <select multiple={ props.multiple } className={ props.className } value={ props.value } onChange={ props.onChange } ref={ props.elRef } onClick={ () => props.onClick?.() }>
4
+ { props.options.map( ( option ) =>
5
+ option.children
6
+ ? <optgroup label={ option.label } key={ option.label }>
7
+ { option.children.map( ( childOption ) =>
8
+ <option key={ childOption.value } value={ childOption.value }>
9
+ { childOption.label }
10
+ </option>,
11
+ ) }
12
+ </optgroup>
13
+ : <option key={ option.value } value={ option.value }>
14
+ { option.label }
15
+ </option>,
16
+ ) }
17
+ </select>
18
+ );
19
+ }
20
+ Select.propTypes = {
21
+ className: PropTypes.string,
22
+ onChange: PropTypes.func,
23
+ options: PropTypes.array,
24
+ elRef: PropTypes.object,
25
+ multiple: PropTypes.bool,
26
+ value: PropTypes.oneOfType( [ PropTypes.array, PropTypes.string ] ),
27
+ onClick: PropTypes.func,
28
+ };
29
+ Select.defaultProps = {
30
+ className: '',
31
+ options: [],
32
+ };
app/assets/js/ui/atoms/text-field.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from '../../utils/utils';
2
+
3
+ import './text-field.scss';
4
+
5
+ export default function TextField( props ) {
6
+ const classNameBase = 'eps-text-field',
7
+ classes = [ classNameBase, props.className, { [ classNameBase + '--outlined' ]: 'outlined' === props.variant } ],
8
+ validProps = { ...props, className: arrayToClassName( classes ) };
9
+
10
+ if ( validProps.multiline ) {
11
+ delete validProps.multiline;
12
+
13
+ return (
14
+ <textarea { ...validProps } />
15
+ );
16
+ }
17
+
18
+ return (
19
+ <input { ...validProps } type="text" />
20
+ );
21
+ }
22
+
23
+ TextField.propTypes = {
24
+ className: PropTypes.string,
25
+ multiline: PropTypes.bool,
26
+ variant: PropTypes.oneOf( [ 'standard', 'outlined' ] ),
27
+ children: PropTypes.string,
28
+ };
29
+
30
+ TextField.defaultProps = {
31
+ className: '',
32
+ variant: 'standard',
33
+ };
app/assets/js/ui/atoms/text-field.scss ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-text-field-color: tints(600);
2
+ $eps-text-field-dark-color: dark-tints(200);
3
+ $eps-text-field-background-color: theme-colors(light);
4
+ $eps-text-field-dark-background-color: dark-tints(700);
5
+ $eps-text-field-outlined-border-color: tints(300);
6
+ $eps-text-field-outlined-dark-border-color: dark-tints(400);
7
+ $eps-text-field-outlined-focus-border-color: tints(400);
8
+ $eps-text-field-outlined-focus-dark-border-color: dark-tints(300);
9
+ $eps-text-field-placeholder-color: tints(500);
10
+ $eps-text-field-placeholder-dark-color: dark-tints(300);
11
+
12
+ @mixin font-style {
13
+ font-family: $eps-font-family;
14
+ font-size: $eps-body-font-size;
15
+ font-weight: $eps-font-weight-base;
16
+ line-height: $eps-line-height-base;
17
+ -webkit-font-smoothing: antialiased;
18
+ -moz-osx-font-smoothing: grayscale;
19
+ }
20
+
21
+ $root: eps-text-field;
22
+
23
+ .#{$root} {
24
+ --eps-text-field-color: #{$eps-text-field-color};
25
+ --eps-text-field-background-color: #{$eps-text-field-background-color};
26
+ --eps-text-field-placeholder-color: #{$eps-text-field-placeholder-color};
27
+ --eps-text-field-outlined-border-color: #{$eps-text-field-outlined-border-color};
28
+ --eps-text-field-outlined-focus-border-color: #{$eps-text-field-outlined-focus-border-color};
29
+
30
+ width: 100%;
31
+ color: var(--eps-text-field-color);
32
+ background-color: var(--eps-text-field-background-color);
33
+ border: 0;
34
+ outline: none;
35
+ @include font-style;
36
+
37
+ &--outlined {
38
+ border-radius: $eps-radius;
39
+ border: $eps-border-width $eps-border-style var(--eps-text-field-outlined-border-color);
40
+ padding: spacing(10);
41
+
42
+ &:focus {
43
+ border-color: var(--eps-text-field-outlined-focus-border-color);
44
+ }
45
+ }
46
+
47
+ &::placeholder {
48
+ color: var(--eps-text-field-placeholder-color);
49
+ @include font-style;
50
+ }
51
+ }
52
+
53
+ .eps-theme-dark {
54
+ .#{$root} {
55
+ --eps-text-field-color: #{$eps-text-field-dark-color};
56
+ --eps-text-field-background-color: #{$eps-text-field-dark-background-color};
57
+ --eps-text-field-placeholder-color: #{$eps-text-field-placeholder-dark-color};
58
+ --eps-text-field-outlined-border-color: #{$eps-text-field-outlined-dark-border-color};
59
+ --eps-text-field-outlined-focus-border-color: #{$eps-text-field-outlined-focus-dark-border-color};
60
+ }
61
+ }
62
+
63
+
app/assets/js/ui/atoms/text.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from '../../utils/utils';
2
+
3
+ export default function Text( props ) {
4
+ const baseClassName = 'eps',
5
+ classes = [
6
+ props.className,
7
+ ],
8
+ variant = props.variant && 'md' !== props.variant ? '-' + props.variant : '';
9
+
10
+ classes.push( baseClassName + '-text' + variant );
11
+
12
+ const Element = () => React.createElement( props.tag, {
13
+ className: arrayToClassName( classes ),
14
+ }, props.children );
15
+
16
+ return <Element />;
17
+ }
18
+
19
+ Text.propTypes = {
20
+ className: PropTypes.string,
21
+ variant: PropTypes.oneOf( [ 'xl', 'lg', 'md', 'sm', 'xs', 'xxs' ] ),
22
+ tag: PropTypes.string,
23
+ children: PropTypes.any.isRequired,
24
+ };
25
+
26
+ Text.defaultProps = {
27
+ className: '',
28
+ tag: 'p',
29
+ };
app/assets/js/ui/card/card-api.scss ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // this should emulate external packages consumption
2
+ @import "../../../styles/common";
3
+
4
+ // Private API. Will consume global tokens
5
+ // - Default tokens
6
+ $card-background-color: rgba(theme-colors(light), $opacity-05);
7
+ $card-background-color-hover: theme-colors(light);
8
+ $card-box-shadow: $eps-box-shadow-2;
9
+ $card-border-radius: $eps-radius;
10
+ $card-transition: $eps-transition-duration;
11
+ $card-font-size: type(text,xs);
12
+ $card-footer-font-size: type(text,xxs);
13
+ $card-padding: spacing(10);
14
+
15
+ $card-header-padding: $card-padding;
16
+ $card-header-footer-border: $eps-border;
17
+ $card-header-footer-active-border: $eps-border-width-md $eps-border-style $eps-border-color-dark;
18
+ $card-header-height: $card-padding * 4;
19
+
20
+ $card-headline-color: tints(600);
21
+
22
+ $card-headline-font-weight: $eps-font-weight-medium;
23
+ $card-headline-line-height: $line-height-2;
24
+
25
+ $card-body-padding-y: $card-padding;
26
+ $card-body-padding-x: $card-padding;
27
+
28
+ $card-figure-background-color: tints(200);
29
+ $card-image-aspect-ratio: var(--card-image-aspect-ratio, #{$ratio-portrait});
30
+
31
+ $card-image-overlay-background-color: rgba(theme-colors(dark), $opacity-02);
32
+ $card-image-overlay-transition: $card-transition;
33
+
34
+ // - Dark tokens
35
+ $card-dark-background-color: dark-tints(600);
36
+ $card-dark-background-color-hover: rgba( dark-tints(500), $opacity-08 );
37
+ $card-dark-box-shadow: $eps-box-shadow;
38
+ $card-dark-header-footer-border: $eps-border-width $eps-border-style dark-tints(700);
39
+ $card-dark-header-footer-active-border: $eps-border-width $eps-border-style dark-tints(800);
40
+ $card-dark-headline-color: dark-tints(100);
41
+ $card-dark-figure-background-color: dark-tints(700);
42
+ $card-dark-image-overlay-background-color: rgba(dark-tints(700), $opacity-05);
43
+
44
+
45
+ :root {
46
+ --card-background-color: #{$card-background-color};
47
+ --card-background-color-hover: #{$card-background-color-hover};
48
+ --card-box-shadow: #{$card-box-shadow};
49
+ --card-header-footer-border: #{$card-header-footer-border};
50
+ --card-header-footer-active-border: #{$card-header-footer-active-border};
51
+ --card-headline-color: #{$card-headline-color};
52
+ --card-figure-background-color: #{$card-figure-background-color};
53
+ --card-image-overlay-background-color: #{$card-image-overlay-background-color};
54
+ }
55
+
56
+ .eps-theme-dark {
57
+ --card-background-color: #{$card-dark-background-color};
58
+ --card-background-color-hover: #{$card-dark-background-color-hover};
59
+ --card-box-shadow: #{$card-dark-box-shadow};
60
+ --card-header-footer-border: #{$card-dark-header-footer-border};
61
+ --card-header-footer-active-border: #{$card-dark-header-footer-active-border};
62
+ --card-headline-color: #{$card-dark-headline-color};
63
+ --card-figure-background-color: #{$card-dark-figure-background-color};
64
+ --card-image-overlay-background-color: #{$card-dark-image-overlay-background-color};
65
+ }
app/assets/js/ui/card/card-body.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName, pxToRem } from 'elementor-app/utils/utils.js';
2
+
3
+ import './card.scss';
4
+
5
+ export default function CardBody( props ) {
6
+ const classNameBase = 'eps-card__body',
7
+ classes = [ classNameBase, props.className ],
8
+ style = {};
9
+
10
+ if ( Object.prototype.hasOwnProperty.call( props, 'padding' ) ) {
11
+ style[ '--eps-card-body-padding' ] = pxToRem( props.padding );
12
+
13
+ classes.push( classNameBase + '--padding' );
14
+ }
15
+
16
+ return (
17
+ <main className={ arrayToClassName( classes ) } style={ style }>
18
+ { props.children }
19
+ </main>
20
+ );
21
+ }
22
+
23
+ CardBody.propTypes = {
24
+ className: PropTypes.string,
25
+ padding: PropTypes.string,
26
+ passive: PropTypes.bool,
27
+ active: PropTypes.bool,
28
+ children: PropTypes.any.isRequired,
29
+ };
30
+
31
+ CardBody.defaultProps = {
32
+ className: '',
33
+ };
app/assets/js/ui/card/card-divider.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import './card.scss';
4
+
5
+ export default function CardDivider( props ) {
6
+ const classNameBase = 'eps-card__divider',
7
+ classes = [ classNameBase, props.className ];
8
+
9
+ return (
10
+ <hr className={ arrayToClassName( classes ) } />
11
+ );
12
+ }
13
+
14
+ CardDivider.propTypes = {
15
+ className: PropTypes.string,
16
+ };
17
+
18
+ CardDivider.defaultProps = {
19
+ className: '',
20
+ };
app/assets/js/ui/card/card-footer.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName, pxToRem } from 'elementor-app/utils/utils.js';
2
+
3
+ import './card.scss';
4
+
5
+ export default function CardFooter( props ) {
6
+ const classNameBase = 'eps-card__footer',
7
+ classes = [ classNameBase, props.className ],
8
+ style = {};
9
+
10
+ if ( Object.prototype.hasOwnProperty.call( props, 'padding' ) ) {
11
+ style[ '--eps-card-footer-padding' ] = pxToRem( props.padding );
12
+
13
+ classes.push( classNameBase + '--padding' );
14
+ }
15
+
16
+ return (
17
+ <footer className={ arrayToClassName( classes ) } style={ style }>
18
+ { props.children }
19
+ </footer>
20
+ );
21
+ }
22
+
23
+ CardFooter.propTypes = {
24
+ className: PropTypes.string,
25
+ padding: PropTypes.string,
26
+ passive: PropTypes.bool,
27
+ active: PropTypes.bool,
28
+ children: PropTypes.object.isRequired,
29
+ };
30
+
31
+ CardFooter.defaultProps = {
32
+ className: '',
33
+ };
app/assets/js/ui/card/card-header.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName, pxToRem } from 'elementor-app/utils/utils.js';
2
+
3
+ import './card.scss';
4
+
5
+ export default function CardHeader( props ) {
6
+ const classNameBase = 'eps-card__header',
7
+ classes = [ classNameBase, props.className ],
8
+ style = {};
9
+
10
+ if ( Object.prototype.hasOwnProperty.call( props, 'padding' ) ) {
11
+ style[ '--eps-card-header-padding' ] = pxToRem( props.padding );
12
+
13
+ classes.push( classNameBase + '--padding' );
14
+ }
15
+
16
+ return (
17
+ <header className={ arrayToClassName( classes ) } style={ style }>
18
+ { props.children }
19
+ </header>
20
+ );
21
+ }
22
+
23
+ CardHeader.propTypes = {
24
+ className: PropTypes.string,
25
+ padding: PropTypes.string,
26
+ passive: PropTypes.bool,
27
+ active: PropTypes.bool,
28
+ children: PropTypes.any.isRequired,
29
+ };
30
+
31
+ CardHeader.defaultProps = {
32
+ className: '',
33
+ };
app/assets/js/ui/card/card-headline.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import './card.scss';
4
+
5
+ export default function CardHeadline( props ) {
6
+ const classNameBase = 'eps-card__headline',
7
+ classes = [ classNameBase, props.className ];
8
+
9
+ return (
10
+ <h4 className={ arrayToClassName( classes ) }>
11
+ { props.children }
12
+ </h4>
13
+ );
14
+ }
15
+
16
+ CardHeadline.propTypes = {
17
+ className: PropTypes.string,
18
+ children: PropTypes.any.isRequired,
19
+ };
20
+
21
+ CardHeadline.defaultProps = {
22
+ className: '',
23
+ };
app/assets/js/ui/card/card-image.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import './card.scss';
2
+
3
+ export default function CardImage( props ) {
4
+ const image = <img src={ props.src } alt={ props.alt } className="eps-card__image" loading="lazy" />;
5
+
6
+ return (
7
+ <figure className={ `eps-card__figure ${ props.className }` }>
8
+ { image }
9
+ { props.children }
10
+ </figure>
11
+ );
12
+ }
13
+
14
+ CardImage.propTypes = {
15
+ className: PropTypes.string,
16
+ src: PropTypes.string.isRequired,
17
+ alt: PropTypes.string.isRequired,
18
+ children: PropTypes.any,
19
+ };
20
+
21
+ CardImage.defaultProps = {
22
+ className: '',
23
+ };
app/assets/js/ui/card/card-overlay.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import './card.scss';
2
+
3
+ export default function CardOverlay( props ) {
4
+ return (
5
+ <div className={ `eps-card__image-overlay ${ props.className }` }>
6
+ { props.children }
7
+ </div>
8
+ );
9
+ }
10
+
11
+ CardOverlay.propTypes = {
12
+ className: PropTypes.string,
13
+ children: PropTypes.object.isRequired,
14
+ };
15
+
16
+ CardOverlay.defaultProps = {
17
+ className: '',
18
+ };
app/assets/js/ui/card/card.js ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import CardHeader from './card-header';
2
+ import CardBody from './card-body';
3
+ import CardImage from './card-image';
4
+ import CardOverlay from './card-overlay';
5
+ import CardFooter from './card-footer';
6
+ import CardHeadline from './card-headline';
7
+ import CardDivider from './card-divider';
8
+
9
+ import './card.scss';
10
+
11
+ const Card = React.forwardRef( ( props, ref ) => {
12
+ return (
13
+ <article className={ `eps-card ${ props.className }` } ref={ ref }>
14
+ { props.children }
15
+ </article>
16
+ );
17
+ } );
18
+
19
+ Card.propTypes = {
20
+ type: PropTypes.string,
21
+ className: PropTypes.string,
22
+ children: PropTypes.any,
23
+ };
24
+
25
+ Card.defaultProps = {
26
+ className: '',
27
+ };
28
+
29
+ Card.displayName = 'Card';
30
+
31
+ Card.Header = CardHeader;
32
+ Card.Body = CardBody;
33
+ Card.Image = CardImage;
34
+ Card.Overlay = CardOverlay;
35
+ Card.Footer = CardFooter;
36
+ Card.Headline = CardHeadline;
37
+ Card.Divider = CardDivider;
38
+
39
+ export default Card;
app/assets/js/ui/card/card.scss ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Card
2
+
3
+ // this should emulate external packages consumption
4
+ @import "./card-api";
5
+
6
+ $root: $eps-prefix + card;
7
+
8
+ .#{$root} {
9
+ background-color: var(--card-background-color);
10
+ box-shadow: $card-box-shadow;
11
+ border-radius: $card-border-radius;
12
+ transition: $card-transition;
13
+ font-size: $card-font-size;
14
+
15
+ &__header {
16
+ padding: $card-header-padding;
17
+ border-bottom: var(--card-header-footer-border);
18
+ min-height: $card-header-height;
19
+ display: flex;
20
+ align-items: center;
21
+
22
+ &--padding {
23
+ padding: var(--eps-card-header-padding);
24
+ }
25
+ }
26
+
27
+ /*
28
+ todo: TBD: Optionally remove headline styling in favor of a global atom depending on variation needs
29
+ */
30
+
31
+ &__headline {
32
+ color: var(--card-headline-color);
33
+ margin-bottom: 0;
34
+ font-weight: $card-headline-font-weight;
35
+ flex-grow: 1;
36
+ @include text-truncate();
37
+ }
38
+
39
+ &__body {
40
+ padding: $card-body-padding-y $card-body-padding-x;
41
+
42
+ &--padding {
43
+ padding: var(--eps-card-body-padding);
44
+ }
45
+ }
46
+
47
+ // figure is here to assist with aspect ratio, and dealing with different image sizes and bg-color for shorter images e.g. header, footer.
48
+ &__figure {
49
+ background-color: var(--card-figure-background-color);
50
+ position: relative;
51
+ padding-bottom: $card-image-aspect-ratio;
52
+ overflow: hidden;
53
+ height: 0;
54
+ }
55
+
56
+ &__image {
57
+ width: 100%;
58
+ object-fit: contain;
59
+ object-position: top;
60
+ position: absolute;
61
+ top: 0;
62
+ left: 0;
63
+ }
64
+
65
+ &__image-overlay {
66
+ position: absolute;
67
+ top: 0;
68
+ background-color: var(--card-image-overlay-background-color);
69
+ z-index: 1;
70
+ width: 100%;
71
+ height: 100%;
72
+ opacity: 0;
73
+ transition: $card-image-overlay-transition;
74
+
75
+ &:hover {
76
+ opacity: 1;
77
+ }
78
+ }
79
+
80
+ &__footer {
81
+ padding: $card-padding;
82
+ border-top: var(--card-header-footer-border);
83
+ font-size: $card-footer-font-size;
84
+
85
+ &--padding {
86
+ padding: var(--eps-card-footer-padding);
87
+ }
88
+ }
89
+
90
+ &:hover {
91
+ background-color: var(--card-background-color-hover);
92
+ }
93
+ }
94
+
app/assets/js/ui/dialog/dialog-actions.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function DialogActions( props ) {
2
+ return (
3
+ <div className="eps-dialog__buttons">
4
+ { props.children }
5
+ </div>
6
+ );
7
+ }
8
+
9
+ DialogActions.propTypes = {
10
+ children: PropTypes.any,
11
+ };
app/assets/js/ui/dialog/dialog-button.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Button from 'elementor-app/ui/molecules/button';
2
+
3
+ export default function DialogButton( props ) {
4
+ return (
5
+ <Button
6
+ { ...props }
7
+ className={ `eps-dialog__button ${ props.className }` }
8
+ />
9
+ );
10
+ }
11
+
12
+ DialogButton.propTypes = {
13
+ ...Button.propTypes,
14
+ tabIndex: PropTypes.string,
15
+ type: PropTypes.string,
16
+ };
17
+
18
+ DialogButton.defaultProps = {
19
+ ...Button.defaultProps,
20
+ tabIndex: '0',
21
+ type: 'button',
22
+ };
app/assets/js/ui/dialog/dialog-content.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function DialogContent( props ) {
2
+ return (
3
+ <div className="eps-dialog__content">
4
+ { props.children }
5
+ </div>
6
+ );
7
+ }
8
+
9
+ DialogContent.propTypes = {
10
+ children: PropTypes.any,
11
+ };
app/assets/js/ui/dialog/dialog-text.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Text from 'elementor-app/ui/atoms/text';
2
+
3
+ export default function DialogText( props ) {
4
+ return (
5
+ <Text variant="xs" { ...props } className={ `eps-dialog__text ${ props.className }` } />
6
+ );
7
+ }
8
+
9
+ DialogText.propTypes = {
10
+ ...Text.propTypes,
11
+ };
12
+
13
+ DialogText.defaultProps = {
14
+ ...Text.defaultProps,
15
+ tag: 'p',
16
+ variant: 'sm',
17
+ };
app/assets/js/ui/dialog/dialog-title.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Heading from 'elementor-app/ui/atoms/heading';
2
+
3
+ export default function DialogTitle( props ) {
4
+ return (
5
+ <Heading
6
+ { ...props }
7
+ className={ `eps-dialog__title ${ props.className }` }
8
+ />
9
+ );
10
+ }
11
+
12
+ DialogTitle.propTypes = {
13
+ ...Heading.propTypes,
14
+ className: PropTypes.string,
15
+ };
16
+
17
+ DialogTitle.defaultProps = {
18
+ ...Heading.propTypes,
19
+ variant: 'h3',
20
+ tag: 'h3',
21
+ className: '',
22
+ };
app/assets/js/ui/dialog/dialog-wrapper.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Button from 'elementor-app/ui/molecules/button';
2
+
3
+ export default function DialogWrapper( props ) {
4
+ let WrapperTag = 'div';
5
+
6
+ if ( props.onSubmit ) {
7
+ WrapperTag = 'form';
8
+ }
9
+
10
+ return (
11
+ <section className="eps-modal__overlay">
12
+ <WrapperTag className="eps-modal eps-dialog" onSubmit={ props.onSubmit }>
13
+ {
14
+ props.onClose &&
15
+ <Button
16
+ onClick={ props.onClose }
17
+ text={ __( 'Close', 'elementor' ) }
18
+ hideText={ true }
19
+ icon="eicon-close"
20
+ className="eps-dialog__close-button"
21
+ />
22
+ }
23
+ { props.children }
24
+ </WrapperTag>
25
+ </section>
26
+ );
27
+ }
28
+
29
+ DialogWrapper.propTypes = {
30
+ onClose: PropTypes.func,
31
+ onSubmit: PropTypes.func,
32
+ children: PropTypes.any,
33
+ };
app/assets/js/ui/dialog/dialog.js ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import DialogWrapper from './dialog-wrapper';
2
+ import DialogContent from './dialog-content';
3
+ import DialogTitle from './dialog-title';
4
+ import DialogText from './dialog-text';
5
+ import DialogActions from './dialog-actions';
6
+ import DialogButton from './dialog-button';
7
+
8
+ import './dialog.scss';
9
+
10
+ export default function Dialog( props ) {
11
+ return (
12
+ <DialogWrapper onSubmit={ props.onSubmit } onClose={ props.onClose }>
13
+ <DialogContent>
14
+ { props.title && <DialogTitle>{ props.title }</DialogTitle> }
15
+ { props.text && <DialogText>{ props.text }</DialogText> }
16
+ { props.children }
17
+ </DialogContent>
18
+ <DialogActions>
19
+ <DialogButton
20
+ key="dismiss"
21
+ text={ props.dismissButtonText }
22
+ onClick={ props.dismissButtonOnClick }
23
+ url={ props.dismissButtonUrl }
24
+ target={ props.dismissButtonTarget }
25
+ // eslint-disable-next-line jsx-a11y/tabindex-no-positive
26
+ tabIndex="2"
27
+ />
28
+ <DialogButton
29
+ key="approve"
30
+ text={ props.approveButtonText }
31
+ onClick={ props.approveButtonOnClick }
32
+ url={ props.approveButtonUrl }
33
+ target={ props.approveButtonTarget }
34
+ color={ props.approveButtonColor }
35
+ elRef={ props.approveButtonRef }
36
+ // eslint-disable-next-line jsx-a11y/tabindex-no-positive
37
+ tabIndex="1"
38
+ />
39
+ </DialogActions>
40
+ </DialogWrapper>
41
+ );
42
+ }
43
+
44
+ Dialog.propTypes = {
45
+ title: PropTypes.any,
46
+ text: PropTypes.any,
47
+ children: PropTypes.any,
48
+ onSubmit: PropTypes.func,
49
+ onClose: PropTypes.func,
50
+ dismissButtonText: PropTypes.string.isRequired,
51
+ dismissButtonOnClick: PropTypes.func,
52
+ dismissButtonUrl: PropTypes.string,
53
+ dismissButtonTarget: PropTypes.string,
54
+ approveButtonText: PropTypes.string.isRequired,
55
+ approveButtonOnClick: PropTypes.func,
56
+ approveButtonUrl: PropTypes.string,
57
+ approveButtonColor: PropTypes.string,
58
+ approveButtonTarget: PropTypes.string,
59
+ approveButtonRef: PropTypes.object,
60
+ };
61
+
62
+ Dialog.defaultProps = {};
63
+
64
+ Dialog.Wrapper = DialogWrapper;
65
+ Dialog.Content = DialogContent;
66
+ Dialog.Title = DialogTitle;
67
+ Dialog.Text = DialogText;
68
+ Dialog.Actions = DialogActions;
69
+ Dialog.Button = DialogButton;
app/assets/js/ui/dialog/dialog.scss ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .eps-dialog {
2
+ border-radius: 3px;
3
+ width: 375px;
4
+
5
+ &__close-button {
6
+ position: absolute;
7
+ top: spacing(44) * -1;
8
+ right: spacing(44) * -1;
9
+ margin-top: spacing(10);
10
+ margin-right: spacing(10);
11
+ z-index: $eps-zindex-modal;
12
+ font-size: type(size, "20");
13
+ color: theme-colors(light);
14
+ }
15
+
16
+ &__content {
17
+ padding: spacing(24) spacing(30) spacing(16);
18
+ font-size: type(text, xs);
19
+ }
20
+
21
+ &__title, &__text {
22
+ text-align: center;
23
+ }
24
+
25
+ &__buttons {
26
+ display: flex;
27
+ }
28
+
29
+ &__button {
30
+ flex: 1;
31
+ border-top: 1px solid var(--hr-color);
32
+ line-height: spacing(44);
33
+ text-align: center;
34
+ justify-content: center;
35
+
36
+ &:last-child:not(:first-child) {
37
+ border-inline-start: 1px solid var(--hr-color);
38
+ }
39
+ }
40
+ }
app/assets/js/ui/grid/grid-api.scss ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @use "sass:math";
2
+ // COMPONENT API: GRID
3
+ // Used specific path here since, pro loads the grid and 'current path' will be wrong.
4
+ @import "../../../../../assets/dev/scss/editor/_breakpoints.scss";
5
+
6
+ // MIXIN: Grid Coll generator
7
+ $eps-grid-columns: 12;
8
+ $eps-grid-spacing-keys: (5 8 10 12 16);
9
+ $grid-spacing-gutter: calc( var(--grid-spacing-gutter) * calc(#{spacing(10)} / 10) );
10
+
11
+ //defining flex traits
12
+ @mixin grid-item-auto($size) {
13
+ .#{$eps-prefix}grid-item-#{$size} {
14
+ flex-grow: 1;
15
+ max-width: 100%;
16
+ flex-basis: 0;
17
+ }
18
+ }
19
+
20
+ // defining cols.
21
+ @mixin grid-col-generator($size, $grid-columns: $eps-grid-columns) {
22
+ @for $i from 1 through $grid-columns {
23
+ .#{$eps-prefix}grid-item-#{$size}-#{$i} {
24
+ flex-grow: 0;
25
+ max-width: calc( #{$i} / #{$grid-columns} * 100% );
26
+ flex-basis: calc( #{$i} / #{$grid-columns} * 100% );
27
+ }
28
+ }
29
+ }
30
+
31
+ // generating spacing classes and custom properties
32
+ @mixin grid-spacing-classes($keys, $amp:'&') {
33
+ @each $key in $keys {
34
+ #{$amp}-#{$key} {
35
+ --grid-spacing-gutter: #{spacing($key)};
36
+ }
37
+ }
38
+ }
39
+
40
+ // exporting custom properties
41
+ @mixin grid-custom-properties {
42
+ :root {
43
+ --grid-spacing-width: calc(100% + var(#{--grid-container-gutters}));
44
+ --grid-spacing-gutters: calc(2 * var(#{--grid-spacing-gutter}));
45
+ }
46
+ }
app/assets/js/ui/grid/grid.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName, pxToRem } from 'elementor-app/utils/utils.js';
2
+
3
+ import './grid.scss';
4
+
5
+ export default function Grid( props ) {
6
+ const propsMap = {
7
+ direction: '--direction{{ -VALUE }}',
8
+ justify: '--justify{{ -VALUE }}',
9
+ alignContent: '--align-content{{ -VALUE }}',
10
+ alignItems: '--align-items{{ -VALUE }}',
11
+ container: '-container',
12
+ item: '-item',
13
+ noWrap: '-container--no-wrap',
14
+ wrapReverse: '-container--wrap-reverse',
15
+ zeroMinWidth: '-item--zero-min-width',
16
+ spacing: '-container--spacing',
17
+ xs: '-item-xs{{ -VALUE }}',
18
+ sm: '-item-sm{{ -VALUE }}',
19
+ md: '-item-md{{ -VALUE }}',
20
+ lg: '-item-lg{{ -VALUE }}',
21
+ xl: '-item-xl{{ -VALUE }}',
22
+ xxl: '-item-xxl{{ -VALUE }}',
23
+ },
24
+ getStyle = () => isValidPropValue( props.spacing ) ? { '--grid-spacing-gutter': pxToRem( props.spacing ) } : {},
25
+ classes = [ getBaseClassName(), props.className, ...getPropsClasses( propsMap, props ) ];
26
+
27
+ return (
28
+ <div style={ getStyle() } className={ arrayToClassName( classes ) }>
29
+ { props.children }
30
+ </div>
31
+ );
32
+ }
33
+
34
+ function getPropsClasses( propsMap, props ) {
35
+ const classes = [];
36
+
37
+ for ( const prop in propsMap ) {
38
+ if ( props[ prop ] ) {
39
+ const propValue = isValidPropValue( props[ prop ] ) ? props[ prop ] : '';
40
+
41
+ classes.push( getBaseClassName() + renderPropValueBrackets( propsMap[ prop ], propValue ) );
42
+ }
43
+ }
44
+
45
+ return classes;
46
+ }
47
+
48
+ function renderPropValueBrackets( propClass, propValue ) {
49
+ const brackets = propClass.match( /{{.*?}}/ );
50
+
51
+ if ( brackets ) {
52
+ const bracketsValue = propValue ? brackets[ 0 ].replace( /[{ }]/g, '' ).replace( /value/i, propValue ) : '';
53
+
54
+ propClass = propClass.replace( brackets[ 0 ], bracketsValue );
55
+ }
56
+
57
+ return propClass;
58
+ }
59
+
60
+ function getBaseClassName() {
61
+ return 'eps-grid';
62
+ }
63
+
64
+ function isValidPropValue( propValue ) {
65
+ return propValue && 'boolean' !== typeof propValue;
66
+ }
67
+
68
+ Grid.propTypes = {
69
+ className: PropTypes.string,
70
+ direction: PropTypes.oneOf( [ 'row', 'column', 'row-reverse', 'column-reverse' ] ),
71
+ justify: PropTypes.oneOf( [ 'start', 'center', 'end', 'space-between', 'space-evenly', 'space-around', 'stretch' ] ),
72
+ alignContent: PropTypes.oneOf( [ 'start', 'center', 'end', 'space-between', 'stretch' ] ),
73
+ alignItems: PropTypes.oneOf( [ 'start', 'center', 'end', 'baseline', 'stretch' ] ),
74
+ container: PropTypes.bool,
75
+ item: PropTypes.bool,
76
+ noWrap: PropTypes.bool,
77
+ wrapReverse: PropTypes.bool,
78
+ zeroMinWidth: PropTypes.bool,
79
+ spacing: PropTypes.number,
80
+ xs: PropTypes.oneOfType( [ PropTypes.number, PropTypes.bool ] ),
81
+ sm: PropTypes.oneOfType( [ PropTypes.number, PropTypes.bool ] ),
82
+ md: PropTypes.oneOfType( [ PropTypes.number, PropTypes.bool ] ),
83
+ lg: PropTypes.oneOfType( [ PropTypes.number, PropTypes.bool ] ),
84
+ xl: PropTypes.oneOfType( [ PropTypes.number, PropTypes.bool ] ),
85
+ xxl: PropTypes.oneOfType( [ PropTypes.number, PropTypes.bool ] ),
86
+ children: PropTypes.any.isRequired,
87
+ };
88
+
89
+ Grid.defaultProps = {
90
+ className: '',
91
+ };
app/assets/js/ui/grid/grid.scss ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // COMPONENT: GRID
2
+ // Defines Grid and Item blocks
3
+
4
+ @import "grid-api";
5
+
6
+
7
+ .#{$eps-prefix}grid {
8
+ // Flex Structure rules
9
+ // apply to all screen size
10
+ // @props: container
11
+ &-container {
12
+ display: flex;
13
+ flex-wrap: wrap;
14
+ width: 100%;
15
+
16
+ // @prop: no-wrap
17
+ &--no-wrap {
18
+ flex-wrap: nowrap;
19
+ }
20
+
21
+ &--wrap-reverse {
22
+ flex-wrap: wrap-reverse;
23
+ }
24
+
25
+ // @prop: spacing[5,8,10,12,16]
26
+ // defined within container only
27
+ // item padding can be 8,10,12,16
28
+ // container width and negative margin are calculated dynamically
29
+ // @markup <Grid container spacing={5} and so on >
30
+ &--spacing {
31
+ --grid-row-gutter: calc(-1 * #{$grid-spacing-gutter});
32
+ width: var(--grid-spacing-width);
33
+ margin: var(--grid-row-gutter);
34
+
35
+ > .#{$eps-prefix}grid-item {
36
+ padding: var(--grid-spacing-gutter);
37
+ }
38
+ }
39
+ }
40
+
41
+ // - direction
42
+ // @props: direction[
43
+ // 'row'
44
+ // 'row-reverse'
45
+ // 'column'
46
+ // 'column-reverse'
47
+ // ]
48
+ &--direction {
49
+ &-row {
50
+ flex-direction: row;
51
+
52
+ &-reverse {
53
+ flex-direction: row-reverse;
54
+ }
55
+ }
56
+
57
+ &-column {
58
+ flex-direction: column;
59
+
60
+ &-reverse {
61
+ flex-direction: column-reverse;
62
+ }
63
+ }
64
+ }
65
+
66
+ // Flex alignments
67
+ // @props: justifyContent[
68
+ // 'stretch'
69
+ // 'start'
70
+ // 'center'
71
+ // 'end'
72
+ // 'space-between'
73
+ // 'space-around'
74
+ // ]
75
+ // default: 'stretch'
76
+ &--justify {
77
+ &-stretch {
78
+ justify-content: stretch;
79
+ }
80
+
81
+ &-start {
82
+ justify-content: flex-start;
83
+ }
84
+
85
+ &-center {
86
+ justify-content: center;
87
+ }
88
+
89
+ &-end {
90
+ justify-content: flex-end;
91
+ }
92
+
93
+ &-space-between {
94
+ justify-content: space-between;
95
+ }
96
+
97
+ &-space-around {
98
+ justify-content: space-around;
99
+ }
100
+
101
+ &-space-evenly {
102
+ justify-content: space-evenly;
103
+ }
104
+ }
105
+
106
+ // @props: alignContent[
107
+ // 'stretch'
108
+ // 'start'
109
+ // 'center'
110
+ // 'end'
111
+ // 'space-between'
112
+ // 'space-around'
113
+ // ]
114
+ // default: 'stretch'
115
+ &--align-content {
116
+ &-stretch {
117
+ align-content: stretch;
118
+ }
119
+
120
+ &-start {
121
+ align-content: flex-start;
122
+ }
123
+
124
+ &-center {
125
+ align-content: center;
126
+ }
127
+
128
+ &-end {
129
+ align-content: flex-end;
130
+ }
131
+
132
+ &-space-between {
133
+ align-content: space-between;
134
+ }
135
+ }
136
+
137
+ // @props: alignItems[
138
+ // 'start'
139
+ // 'center'
140
+ // 'end'
141
+ // 'baseline'
142
+ // 'stretch'
143
+ // ]
144
+ // default: 'stretch'
145
+ &--align-items {
146
+ &-start {
147
+ align-items: flex-start;
148
+ }
149
+
150
+ &-center {
151
+ align-items: center;
152
+ }
153
+
154
+ &-end {
155
+ align-items: flex-end;
156
+ }
157
+
158
+ &-baseline {
159
+ align-items: baseline;
160
+ }
161
+
162
+ &-stretch {
163
+ align-items: stretch;
164
+ }
165
+ }
166
+ }
167
+
168
+ // - Grid Item assumes display flex and wrap on parent
169
+ // - Defining Item Base Properties
170
+ // - Generating flex item traits within @media breakpoints
171
+
172
+ // Zero min width to disable overflow while container is no wrap
173
+
174
+ // @prop: ZeroMinWidth
175
+ // @markup <Grid item zeroMinWidth> or <Grid item sm> and so on..
176
+ .#{$eps-prefix}grid-item--zero-min-width {
177
+ min-width: 0;
178
+ }
179
+
180
+ // Item flex sizing, auto grid
181
+ // @prop: [xs,sm,md,lg,xl,xxl]
182
+ // @markup <Grid item xs> or <Grid item sm> and so on..
183
+ // No need for container:
184
+ // @prop: [xs={@num<=12}],sm={@num<=12},md={@num<=12},lg={@num<=12},xl={@num<=12},xxl={@num<=12}]
185
+ // @markup <Grid item xs={4}> or <Grid item sm={3}> and so on..
186
+ @each $breakpoint in map-keys($breakpoints) {
187
+ @media screen and (min-width: #{map-get($breakpoints, $breakpoint)}) {
188
+ @include grid-item-auto($breakpoint);
189
+ }
190
+ }
191
+
192
+ // Generating cols per each media breakpoint
193
+ @include grid-col-generator(xs);
194
+
195
+ @media screen and (min-width: $editor-screen-sm-min) {
196
+ @include grid-col-generator(sm);
197
+ }
198
+
199
+ @media screen and (min-width: $editor-screen-md-min) {
200
+ @include grid-col-generator(md);
201
+ }
202
+
203
+ @media screen and (min-width: $editor-screen-lg-min) {
204
+ @include grid-col-generator(lg);
205
+ }
206
+
207
+ @media screen and (min-width: $editor-screen-xl-min) {
208
+ @include grid-col-generator(xl);
209
+ }
210
+
211
+ @media screen and (min-width: $editor-screen-xxl-min) {
212
+ @include grid-col-generator(xxl);
213
+ }
app/assets/js/ui/menu/menu-item.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import './menu-item.scss';
2
+ import BaseButton from '../molecules/button';
3
+
4
+ export default class SideMenuItem extends BaseButton {
5
+ getCssId() {
6
+ return `eps-menu-item-` + super.getCssId();
7
+ }
8
+
9
+ getClassName() {
10
+ return `eps-menu-item ` + super.getClassName();
11
+ }
12
+ }
app/assets/js/ui/menu/menu-item.scss ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $menu-item-font-size: $eps-font-size-caption;
2
+ $menu-item-line-height: $eps-line-height-sm;
3
+ $menu-item-link-spacing: spacing(12);
4
+ $menu-item-icon-size: $font-size-18;
5
+ $menu-item-padding-y: spacing(8);
6
+ $menu-item-padding-x: spacing(30);
7
+ $menu-item-height: spacing(44);
8
+ $menu-item-pointer-width: spacing(5);
9
+ $menu-item-pointer-color: theme-elements-colors(link-color);
10
+ $menu-item-action-button-padding: spacing(10);
11
+ $menu-item-action-button-margin: spacing(20);
12
+ $menu-item-action-button-color: tints(300);
13
+
14
+ $menu-item-active-box-shadow: $eps-box-shadow;
15
+ $menu-item-transition: $eps-transition-duration;
16
+
17
+ $menu-item-color: tints(600);
18
+ $menu-item-color-hover: tints(700);
19
+ $menu-item-background-color-active: theme-colors(light);
20
+ $menu-item-icon-color: tints(500);
21
+
22
+ :root {
23
+ --menu-item-color: #{$menu-item-color};
24
+ --menu-item-color-hover: #{$menu-item-color-hover};
25
+ --menu-item-background-color-active: #{$menu-item-background-color-active};
26
+ --menu-item-icon-color: #{$menu-item-icon-color};
27
+ --menu-item-action-button-color: #{$menu-item-action-button-color};
28
+ }
29
+
30
+ $menu-item-dark-color: dark-tints(100);
31
+ $menu-item-dark-color-hover: dark-tints(100);
32
+ $menu-item-dark-background-color-active: dark-tints(600);
33
+ $menu-item-icon-dark-color: dark-tints(200);
34
+ $menu-item-action-button-dark-color: dark-tints(400);
35
+
36
+ .eps-theme-dark {
37
+ --menu-item-color: #{$menu-item-dark-color};
38
+ --menu-item-color-hover: #{$menu-item-dark-color-hover};
39
+ --menu-item-background-color-active: #{$menu-item-dark-background-color-active};
40
+ --menu-item-icon-color: #{$menu-item-icon-dark-color};
41
+ --menu-item-action-button-color: #{$menu-item-action-button-dark-color};
42
+ }
43
+
44
+ .#{$eps-prefix}menu-item {
45
+ display: flex;
46
+ align-items: center;
47
+ position: relative;
48
+ transition: $menu-item-transition;
49
+ --action-button-opacity: 0;
50
+
51
+ &::before {
52
+ content: '';
53
+ display: block;
54
+ position: absolute;
55
+ top: 0;
56
+ inset-inline-start: 0;
57
+ height: 100%;
58
+ width: var(--menu-item-pointer-width);
59
+ background-color: $menu-item-pointer-color;
60
+ }
61
+
62
+ &:hover {
63
+ --action-button-opacity: 1;
64
+ }
65
+
66
+ &:hover, &--active {
67
+ --menu-item-color: var(--menu-item-color-hover);
68
+ --eps-link-color: var(--menu-item-color-hover);
69
+ --menu-item-icon-color: #{$menu-item-pointer-color};
70
+ }
71
+
72
+ &--active {
73
+ background-color: var(--menu-item-background-color-active);
74
+ }
75
+
76
+ &__link {
77
+ padding: $menu-item-padding-y $menu-item-padding-x;
78
+ min-height: $menu-item-height;
79
+ font-size: $menu-item-font-size;
80
+ line-height: $menu-item-line-height;
81
+ flex-grow: 1;
82
+ display: flex;
83
+ align-items: center;
84
+ color: var(--menu-item-color);
85
+ --eps-link-color: var(--menu-item-color);
86
+
87
+ &:not(:last-child) {
88
+ padding-inline-end: 0;
89
+ }
90
+
91
+ .eps-icon {
92
+ font-size: $menu-item-icon-size;
93
+ color: var(--menu-item-icon-color);
94
+ margin-inline-end: $menu-item-link-spacing;
95
+ }
96
+ }
97
+
98
+ &__action-button {
99
+ color: var(--menu-item-action-button-color);
100
+ opacity: var(--action-button-opacity);
101
+ padding: $menu-item-action-button-padding;
102
+ transition: $menu-item-transition;
103
+ margin-inline-end: $menu-item-action-button-margin;
104
+ }
105
+
106
+ &--active {
107
+ --menu-item-pointer-width: #{$menu-item-pointer-width};
108
+ box-shadow: $menu-item-active-box-shadow;
109
+
110
+ .#{$eps-prefix}menu-item__link .eps-icon {
111
+ color: $menu-item-pointer-color;
112
+ }
113
+ }
114
+ }
app/assets/js/ui/menu/menu.js ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import './menu.scss';
2
+ import Button from '../molecules/button';
3
+ import router from '@elementor/router';
4
+ import { Match, LocationProvider } from '@reach/router';
5
+
6
+ export default function Menu( props ) {
7
+ const ActionButton = ( itemProps ) => {
8
+ if ( ! props.actionButton ) {
9
+ return '';
10
+ }
11
+
12
+ return props.actionButton( itemProps );
13
+ };
14
+
15
+ if ( props.promotion ) {
16
+ return (
17
+ <nav className="eps-menu">
18
+ { props.children }
19
+ <ul>
20
+ { props.menuItems.map( ( item ) => (
21
+ <li key={ item.type } className="eps-menu-item">
22
+ <Button text={ item.title } className="eps-menu-item__link" { ...item } />
23
+ <ActionButton { ...item } />
24
+ </li>
25
+ ) ) }
26
+ </ul>
27
+ </nav>
28
+ );
29
+ }
30
+
31
+ return (
32
+ <LocationProvider history={ router.appHistory }>
33
+ <nav className="eps-menu">
34
+ { props.children }
35
+ <ul>
36
+ { (
37
+ props.menuItems.map( ( item ) => (
38
+ <Match key={ item.type } path={ item.url }>
39
+ { ( { match } ) => {
40
+ return (
41
+ <li key={ item.type } className={ `eps-menu-item${ match ? ' eps-menu-item--active' : '' }` }>
42
+ <Button text={ item.title } className="eps-menu-item__link" { ...item } />
43
+ <ActionButton { ...item } />
44
+ </li>
45
+ );
46
+ } }
47
+ </Match>
48
+ ) )
49
+ ) }
50
+ </ul>
51
+ </nav>
52
+ </LocationProvider>
53
+ );
54
+ }
55
+
56
+ Menu.propTypes = {
57
+ menuItems: PropTypes.arrayOf( PropTypes.object ),
58
+ children: PropTypes.any,
59
+ actionButton: PropTypes.func,
60
+ promotion: PropTypes.bool,
61
+ };
app/assets/js/ui/menu/menu.scss ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $menu-title-font-size: type(text,xxs);
2
+ $menu-title-line-height: type(line-height,sm);
3
+ $menu-title-padding-y: spacing(8);
4
+ $menu-title-padding-x: spacing(30);
5
+ $menu-item-height: spacing(44);
6
+
7
+ // Default Color scheme
8
+ $menu-title-color: tints(600);
9
+
10
+ // Dark Color scheme
11
+ $menu-title-dark-color: tints(600);
12
+
13
+ :root {
14
+ --menu-title-color: #{$menu-title-color};
15
+ }
16
+
17
+ .eps-theme-dark {
18
+ --menu-title-color: #{$menu-title-dark-color};
19
+ }
20
+
21
+ .#{$eps-prefix}menu {
22
+
23
+ ul {
24
+ list-style: none;
25
+ padding: 0;
26
+ margin: 0;
27
+
28
+ li {
29
+ display: flex;
30
+ }
31
+ }
32
+
33
+ &__title {
34
+ padding: $menu-title-padding-y $menu-title-padding-x;
35
+ font-size: $menu-title-font-size;
36
+ line-height: $menu-title-line-height;
37
+ text-transform: uppercase;
38
+ font-weight: normal;
39
+ color: var(--menu-title-color);
40
+ }
41
+ }
app/assets/js/ui/modal/modal-section.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from '../../utils/utils';
2
+
3
+ export default function ModalSection( props ) {
4
+ return (
5
+ <section className={ arrayToClassName( [ 'eps-modal__section', props.className ] ) }>
6
+ { props.children }
7
+ </section>
8
+ );
9
+ }
10
+
11
+ ModalSection.propTypes = {
12
+ className: PropTypes.string,
13
+ children: PropTypes.any,
14
+ };
15
+
16
+ ModalSection.defaultProps = {
17
+ className: '',
18
+ };
app/assets/js/ui/modal/modal-tip.js ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from '../../utils/utils';
2
+
3
+ import Heading from 'elementor-app/ui/atoms/heading';
4
+ import Text from 'elementor-app/ui/atoms/text';
5
+
6
+ export default function ModalTip( props ) {
7
+ return (
8
+ <div className={ arrayToClassName( [ 'eps-modal__tip', props.className ] ) }>
9
+ <Heading variant="h3" tag="h3">{ props.title }</Heading>
10
+ { props.description && <Text variant="xs">{ props.description }</Text> }
11
+ </div>
12
+ );
13
+ }
14
+
15
+ ModalTip.propTypes = {
16
+ className: PropTypes.string,
17
+ title: PropTypes.string,
18
+ description: PropTypes.string,
19
+ };
20
+
21
+ ModalTip.defaultProps = {
22
+ className: '',
23
+ title: __( 'Tip', 'elementor' ),
24
+ };
app/assets/js/ui/modal/modal.js ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useRef, useEffect } from 'react';
2
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
3
+
4
+ import Button from 'elementor-app/ui/molecules/button';
5
+ import Grid from 'elementor-app/ui/grid/grid';
6
+ import Icon from 'elementor-app/ui/atoms/icon';
7
+ import Text from 'elementor-app/ui/atoms/text';
8
+
9
+ import ModalSection from './modal-section';
10
+ import ModalTip from './modal-tip';
11
+
12
+ import './modal.scss';
13
+
14
+ export default function ModalProvider( props ) {
15
+ const [ show, setShow ] = useState( props.show ),
16
+ hideModal = () => {
17
+ setShow( false );
18
+
19
+ // The purpose of the props.setShow is to sync an external state with the component inner state.
20
+ if ( props.setShow ) {
21
+ props.setShow( false );
22
+ }
23
+ },
24
+ showModal = () => {
25
+ setShow( true );
26
+
27
+ // The purpose of the props.setShow is to sync an external state with the component inner state.
28
+ if ( props.setShow ) {
29
+ props.setShow( true );
30
+ }
31
+ },
32
+ modalAttrs = {
33
+ ...props,
34
+ show,
35
+ hideModal,
36
+ showModal,
37
+ };
38
+
39
+ useEffect( () => {
40
+ // Sync with external state.
41
+ setShow( props.show );
42
+ }, [ props.show ] );
43
+
44
+ return (
45
+ <>
46
+ {
47
+ props.toggleButtonProps &&
48
+ <Button { ...props.toggleButtonProps } onClick={ showModal } />
49
+ }
50
+
51
+ <Modal { ...modalAttrs }>
52
+ { props.children }
53
+ </Modal>
54
+ </>
55
+ );
56
+ }
57
+
58
+ ModalProvider.propTypes = {
59
+ children: PropTypes.node.isRequired,
60
+ toggleButtonProps: PropTypes.object,
61
+ title: PropTypes.string,
62
+ icon: PropTypes.string,
63
+ show: PropTypes.bool,
64
+ setShow: PropTypes.func,
65
+ onOpen: PropTypes.func,
66
+ onClose: PropTypes.func,
67
+ };
68
+
69
+ ModalProvider.defaultProps = {
70
+ show: false,
71
+ };
72
+
73
+ ModalProvider.Section = ModalSection;
74
+ ModalProvider.Tip = ModalTip;
75
+
76
+ export const Modal = ( props ) => {
77
+ const modalRef = useRef( null ),
78
+ closeRef = useRef( null ),
79
+ closeModal = ( e ) => {
80
+ const node = modalRef.current,
81
+ closeNode = closeRef.current,
82
+ isInCloseNode = closeNode && closeNode.contains( e.target );
83
+
84
+ // Ignore if click is inside the modal
85
+ if ( node && node.contains( e.target ) && ! isInCloseNode ) {
86
+ return;
87
+ }
88
+
89
+ props.hideModal();
90
+
91
+ if ( props.onClose ) {
92
+ props.onClose( e );
93
+ }
94
+ };
95
+
96
+ useEffect( () => {
97
+ if ( props.show ) {
98
+ document.addEventListener( 'mousedown', closeModal, false );
99
+ props.onOpen?.();
100
+ }
101
+
102
+ return () => document.removeEventListener( 'mousedown', closeModal, false );
103
+ }, [ props.show ] );
104
+
105
+ if ( ! props.show ) {
106
+ return null;
107
+ }
108
+
109
+ return (
110
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
111
+ <div className="eps-modal__overlay" onClick={ closeModal }>
112
+ <div className={ arrayToClassName( [ 'eps-modal', props.className ] ) } ref={ modalRef } >
113
+ <Grid container className="eps-modal__header" justify="space-between" alignItems="center">
114
+ <Grid item>
115
+ <Icon className={ `eps-modal__icon ${ props.icon }` } />
116
+ <Text className="title" tag="span">{ props.title }</Text>
117
+ </Grid>
118
+ <Grid item>
119
+ <div className="eps-modal__close-wrapper" ref={ closeRef }>
120
+ <Button text={ __( 'Close', 'elementor' ) } hideText icon="eicon-close" onClick={ props.closeModal } />
121
+ </div>
122
+ </Grid>
123
+ </Grid>
124
+ <div className="eps-modal__body">
125
+ { props.children }
126
+ </div>
127
+ </div>
128
+ </div>
129
+ );
130
+ };
131
+
132
+ Modal.propTypes = {
133
+ className: PropTypes.string,
134
+ children: PropTypes.any.isRequired,
135
+ title: PropTypes.string.isRequired,
136
+ icon: PropTypes.string,
137
+ show: PropTypes.bool,
138
+ setShow: PropTypes.func,
139
+ hideModal: PropTypes.func,
140
+ showModal: PropTypes.func,
141
+ closeModal: PropTypes.func,
142
+ onOpen: PropTypes.func,
143
+ onClose: PropTypes.func,
144
+ };
145
+
146
+ Modal.defaultProps = {
147
+ className: '',
148
+ };
app/assets/js/ui/modal/modal.scss ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-modal-background-color: theme-colors(light);
2
+ $eps-modal-width: px-to-rem(700);
3
+ $eps-modal-border-radius: $eps-radius;
4
+ $eps-modal-overlay: rgba(theme-colors(dark), $opacity-05);
5
+ $eps-modal-zindex: $eps-zindex-modal-backdrop;
6
+ $eps-modal-header-type: type(text, md);
7
+ $eps-modal-header-background-color: theme-colors(info);
8
+ $eps-modal-header-color: theme-colors(light);
9
+ $eps-modal-header-height: spacing(44);
10
+ $eps-modal-header-padding: spacing(10) spacing(16);
11
+ $eps-modal-header-border-radius: $eps-radius;
12
+ $eps-modal-icon-spacing: spacing(10);
13
+ $eps-modal-body-padding: spacing(30);
14
+ $eps-modal-close-button-spacing: spacing(16);
15
+ $eps-modal-close-button-border: solid 1px theme-colors(light);
16
+ $eps-modal-animation: $eps-pop-animation;
17
+ $eps-tip-padding-start: spacing(12);
18
+ $eps-tip-border-start: 3px solid theme-colors(info);
19
+ $eps-tip-margin-bottom: spacing(30);
20
+ $eps-tip-margin-top: spacing(30);
21
+
22
+ :root {
23
+ --eps-modal-background-color: #{$eps-modal-background-color};
24
+ --eps-modal-header-background-color: #{$eps-modal-header-background-color};
25
+ }
26
+
27
+ // Dark
28
+
29
+ $eps-dark-modal-background-color: dark-tints(700);
30
+ $eps-dark-modal-header-background-color: dark-theme-colors(info);
31
+
32
+ .eps-theme-dark {
33
+ --eps-modal-background-color: #{$eps-dark-modal-background-color};
34
+ --eps-modal-header-background-color: #{$eps-dark-modal-header-background-color};
35
+ }
36
+
37
+ /** ----------------------------------------------------------------
38
+ EPS Modal
39
+ ---------------------------------------------------------------- */
40
+
41
+ .#{$eps-prefix}modal {
42
+ max-width: $eps-modal-width;
43
+ background: var(--eps-modal-background-color);
44
+ border-radius: $eps-modal-border-radius;
45
+ animation: $eps-modal-animation;
46
+
47
+ &__overlay {
48
+ background: $eps-modal-overlay;
49
+ position: fixed;
50
+ display: flex;
51
+ top: 0;
52
+ left: 0;
53
+ width: 100%;
54
+ height: 100%;
55
+ align-items: center;
56
+ justify-content: center;
57
+ z-index: $eps-modal-zindex;
58
+ }
59
+
60
+ &__header {
61
+ font-size: $eps-modal-header-type;
62
+ background: var(--eps-modal-header-background-color);
63
+ height: $eps-modal-header-height;
64
+ padding: $eps-modal-header-padding;
65
+ border-radius: $eps-modal-header-border-radius;
66
+
67
+ &,
68
+ & .title {
69
+ color: $eps-modal-header-color;
70
+ }
71
+ }
72
+
73
+ &__icon {
74
+ margin-inline-end: $eps-modal-icon-spacing;
75
+ }
76
+
77
+ &__body {
78
+ padding: $eps-modal-body-padding;
79
+ }
80
+
81
+ &__tip, .#{$eps-prefix}tip {
82
+ padding-inline-start: $eps-tip-padding-start;
83
+ border-inline-start: $eps-tip-border-start;
84
+ &:not(:last-child) {
85
+ margin-bottom: $eps-tip-margin-bottom;
86
+ }
87
+
88
+ &:not(:first-child) {
89
+ margin-top: $eps-tip-margin-top;
90
+ }
91
+ }
92
+
93
+ &__section:not(:first-child) {
94
+ margin-top: spacing(30);
95
+ }
96
+
97
+ &__close-wrapper {
98
+ padding-inline-start: $eps-modal-close-button-spacing;
99
+ border-inline-start: $eps-modal-close-button-border;
100
+ }
101
+ }
app/assets/js/ui/molecules/add-new-button.js ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Button from './button';
2
+
3
+ import './add-new-button.scss';
4
+
5
+ export default class AddNewButton extends Button {
6
+ getClassName() {
7
+ let className = this.props.className;
8
+
9
+ if ( this.props.size ) {
10
+ className += ' eps-add-new-button--' + this.props.size;
11
+ }
12
+
13
+ return className;
14
+ }
15
+
16
+ static propTypes = {
17
+ ...Button.propTypes,
18
+ text: PropTypes.string,
19
+ size: PropTypes.string,
20
+ };
21
+
22
+ static defaultProps = {
23
+ ...Button.defaultProps,
24
+ className: 'eps-add-new-button',
25
+ text: __( 'Add New', 'elementor-pro' ),
26
+ icon: 'eicon-plus',
27
+ };
28
+ }
app/assets/js/ui/molecules/add-new-button.scss ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-add-new-button-background-color: theme-colors(info);
2
+ $eps-add-new-button-icon-color: theme-colors(light);
3
+ $eps-add-new-button-size: spacing(24);
4
+ $eps-add-new-button-size-sm: spacing(16);
5
+ $eps-add-new-button-icon-size-ratio: 0.75;
6
+ $eps-add-new-button-font-weight: $eps-font-weight-medium;
7
+
8
+ .#{$eps-prefix}add-new-button {
9
+ display: inline-flex;
10
+ --eps-add-new-button-size: #{$eps-add-new-button-size};
11
+ line-height: var(--eps-add-new-button-size);
12
+ cursor: pointer;
13
+
14
+ .eps-icon {
15
+ background-color: $eps-add-new-button-background-color;
16
+ color: $eps-add-new-button-icon-color;
17
+ width: var(--eps-add-new-button-size);
18
+ height: var(--eps-add-new-button-size);
19
+ border-radius: 100%;
20
+ font-size: calc(var(--eps-add-new-button-size) * #{$eps-add-new-button-icon-size-ratio});
21
+ text-align: center;
22
+ line-height: var(--eps-add-new-button-size);
23
+ }
24
+
25
+ span:not(.sr-only) {
26
+ margin-inline-start: spacing(10);
27
+ font-weight: $eps-add-new-button-font-weight;
28
+ }
29
+
30
+ &--sm {
31
+ --eps-add-new-button-size: #{$eps-add-new-button-size-sm};
32
+ }
33
+ }
app/assets/js/ui/molecules/button.js ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Link, LocationProvider } from '@reach/router';
2
+ import router from '@elementor/router';
3
+ import Icon from 'elementor-app/ui/atoms/icon';
4
+
5
+ export default class Button extends React.Component {
6
+ static propTypes = {
7
+ text: PropTypes.string.isRequired,
8
+ hideText: PropTypes.bool,
9
+ icon: PropTypes.string,
10
+ tooltip: PropTypes.string,
11
+ id: PropTypes.string,
12
+ className: PropTypes.string,
13
+ url: PropTypes.string,
14
+ onClick: PropTypes.func,
15
+ variant: PropTypes.oneOf( [ 'contained', 'underlined', 'outlined', '' ] ),
16
+ color: PropTypes.oneOf( [ 'primary', 'secondary', 'cta', 'link', 'disabled' ] ),
17
+ size: PropTypes.oneOf( [ 'sm', 'md', 'lg' ] ),
18
+ target: PropTypes.string,
19
+ rel: PropTypes.string,
20
+ elRef: PropTypes.object,
21
+ };
22
+
23
+ static defaultProps = {
24
+ id: '',
25
+ className: '',
26
+ variant: '',
27
+ target: '_parent',
28
+ };
29
+
30
+ getCssId() {
31
+ return this.props.id;
32
+ }
33
+
34
+ getClassName() {
35
+ const baseClassName = 'eps-button',
36
+ classes = [ baseClassName, this.props.className ];
37
+
38
+ return classes
39
+ .concat( this.getStylePropsClasses( baseClassName ) )
40
+ .filter( ( classItem ) => '' !== classItem )
41
+ .join( ' ' );
42
+ }
43
+
44
+ getStylePropsClasses( baseClassName ) {
45
+ const styleProps = [ 'color', 'size', 'variant' ],
46
+ stylePropClasses = [];
47
+
48
+ styleProps.forEach( ( styleProp ) => {
49
+ const stylePropValue = this.props[ styleProp ];
50
+
51
+ if ( stylePropValue ) {
52
+ stylePropClasses.push( baseClassName + '--' + stylePropValue );
53
+ }
54
+ } );
55
+
56
+ return stylePropClasses;
57
+ }
58
+
59
+ getIcon() {
60
+ if ( this.props.icon ) {
61
+ const tooltip = this.props.tooltip || this.props.text;
62
+ const icon = <Icon className={ this.props.icon } aria-hidden="true" title={ tooltip } />;
63
+ let screenReaderText = '';
64
+
65
+ if ( this.props.hideText ) {
66
+ screenReaderText = <span className="sr-only" >{ tooltip }</span>;
67
+ }
68
+
69
+ return (
70
+ <>
71
+ { icon }
72
+ { screenReaderText }
73
+ </>
74
+ );
75
+ }
76
+ return '';
77
+ }
78
+
79
+ getText() {
80
+ return this.props.hideText ? '' : <span>{ this.props.text }</span>;
81
+ }
82
+
83
+ render() {
84
+ const attributes = {},
85
+ id = this.getCssId(),
86
+ className = this.getClassName();
87
+
88
+ // Add attributes only if they are not empty.
89
+ if ( id ) {
90
+ attributes.id = id;
91
+ }
92
+
93
+ if ( className ) {
94
+ attributes.className = className;
95
+ }
96
+
97
+ if ( this.props.onClick ) {
98
+ attributes.onClick = this.props.onClick;
99
+ }
100
+
101
+ if ( this.props.rel ) {
102
+ attributes.rel = this.props.rel;
103
+ }
104
+
105
+ if ( this.props.elRef ) {
106
+ attributes.ref = this.props.elRef;
107
+ }
108
+
109
+ const buttonContent = (
110
+ <>
111
+ { this.getIcon() }
112
+ { this.getText() }
113
+ </>
114
+ );
115
+
116
+ if ( this.props.url ) {
117
+ if ( 0 === this.props.url.indexOf( 'http' ) ) {
118
+ return (
119
+ <a href={ this.props.url } target={ this.props.target } { ...attributes }>
120
+ { buttonContent }
121
+ </a>
122
+ );
123
+ }
124
+
125
+ // @see https://reach.tech/router/example/active-links.
126
+ attributes.getProps = ( props ) => {
127
+ if ( props.isCurrent ) {
128
+ attributes.className += ' active';
129
+ }
130
+
131
+ return {
132
+ className: attributes.className,
133
+ };
134
+ };
135
+
136
+ return (
137
+ <LocationProvider history={ router.appHistory }>
138
+ <Link to={ this.props.url } { ...attributes } >
139
+ { buttonContent }
140
+ </Link>
141
+ </LocationProvider>
142
+ );
143
+ }
144
+
145
+ return (
146
+ <div { ...attributes }>
147
+ { buttonContent }
148
+ </div>
149
+ );
150
+ }
151
+ }
app/assets/js/ui/molecules/buttons-api.scss ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // BTN API
2
+ $button-font-weight: $eps-font-weight-medium;
3
+ $button-line-height: 16px;
4
+ $button-padding-y: 0.5em;
5
+ $button-padding-x: 1.5em;
6
+ $button-border-radius: $eps-radius;
7
+ $button-hover-constant: 5%;
8
+ $button-active-constant: 3%;
9
+ // sizing
10
+ // -- sm
11
+ $button-sm-font-size: type(text,xs);
12
+ $button-sm-line-height: 14px;
13
+ // --lg
14
+ $button-lg-font-size: type(text,lg);
15
+ $button-lg-line-height: 18px;
16
+ //Semantic Colors
17
+ $button-primary-background-color: theme-colors(success);
18
+ $button-primary-hover-background-color: darken($button-primary-background-color, $button-hover-constant);
19
+ $button-primary-active-background-color: darken($button-primary-background-color, $button-active-constant);
20
+ $button-primary-color: theme-colors(light);
21
+ $button-secondary-background-color: tints(500);
22
+ $button-secondary-hover-background-color: darken($button-secondary-background-color, $button-hover-constant);
23
+ $button-secondary-active-background-color: darken($button-secondary-background-color, $button-active-constant);
24
+ $button-secondary-color: theme-colors(light);
25
+ $button-danger-background-color: theme-colors(danger);
26
+ $button-danger-hover-background-color: darken($button-danger-background-color, $button-hover-constant);
27
+ $button-danger-active-background-color: darken($button-danger-background-color, $button-active-constant);
28
+ $button-danger-color: theme-colors(light);
29
+ $button-cta-background-color: theme-colors(cta);
30
+ $button-cta-hover-background-color: darken($button-cta-background-color, $button-hover-constant);
31
+ $button-cta-active-background-color: darken($button-cta-background-color, $button-active-constant);
32
+ $button-cta-color: theme-colors(light);
33
+ $button-link-background-color: theme-elements-colors(link-color);
34
+ $button-link-hover-background-color: darken($button-link-background-color, $button-hover-constant);
35
+ $button-link-active-background-color: darken($button-link-background-color, $button-active-constant);
36
+ $button-link-color: theme-colors(light);
37
+ $button-disabled-background-color: theme-colors(disabled);
38
+ $button-disabled-hover-background-color: darken($button-disabled-background-color, $button-hover-constant);
39
+ $button-disabled-active-background-color: darken($button-disabled-background-color, $button-active-constant);
40
+ $button-disabled-color: theme-colors(light);
41
+ // -- dark
42
+ $button-dark-primary-background-color: dark-theme-colors(success);
43
+ $button-dark-primary-color: dark-theme-colors(light);
44
+ $button-dark-primary-hover-background-color: darken($button-dark-primary-background-color, $button-hover-constant);
45
+ $button-dark-primary-active-background-color: darken($button-dark-primary-background-color, $button-active-constant);
46
+ $button-dark-secondary-background-color: dark-tints(200);
47
+ $button-dark-secondary-hover-background-color: darken($button-dark-secondary-background-color, $button-hover-constant);
48
+ $button-dark-secondary-active-background-color: darken($button-dark-secondary-background-color, $button-active-constant);
49
+ $button-dark-secondary-color: dark-theme-colors(light);
50
+ $button-dark-danger-background-color: dark-theme-colors(danger);
51
+ $button-dark-danger-hover-background-color: darken($button-dark-danger-background-color, $button-hover-constant);
52
+ $button-dark-danger-active-background-color: darken($button-dark-danger-background-color, $button-active-constant);
53
+ $button-dark-danger-color: dark-theme-colors(light);
54
+ $button-dark-cta-background-color: dark-theme-colors(cta);
55
+ $button-dark-cta-hover-background-color: darken($button-dark-cta-background-color, $button-hover-constant);
56
+ $button-dark-cta-active-background-color: darken($button-dark-cta-background-color, $button-active-constant);
57
+ $button-dark-cta-color: dark-theme-colors(light);
58
+ $button-dark-link-background-color: theme-elements-colors(link-color);
59
+ $button-dark-link-hover-background-color: darken($button-dark-link-background-color, $button-hover-constant);
60
+ $button-dark-link-active-background-color: darken($button-dark-link-background-color, $button-active-constant);
61
+ $button-dark-link-color: theme-colors(light);
62
+ $button-dark-disabled-background-color: dark-theme-colors(disabled);
63
+ $button-dark-disabled-hover-background-color: darken($button-dark-disabled-background-color, $button-hover-constant);
64
+ $button-dark-disabled-active-background-color: darken($button-dark-disabled-background-color, $button-active-constant);
65
+ $button-dark-disabled-color: dark-theme-colors(light);
66
+
67
+ // using mixing to transfer custom properties
68
+ @mixin button-custom-properties() {
69
+ --button-line-height: #{$button-line-height};
70
+ --button-padding-y: #{$button-padding-y};
71
+ --button-padding-x: #{$button-padding-x};
72
+ // primary
73
+ --button-primary-background-color: #{$button-primary-background-color};
74
+ --button-primary-hover-background-color: #{$button-primary-hover-background-color};
75
+ --button-primary-active-background-color: #{$button-primary-active-background-color};
76
+ --button-primary-color: #{$button-primary-color};
77
+ // secondary
78
+ --button-secondary-background-color: #{$button-secondary-background-color};
79
+ --button-secondary-hover-background-color: #{$button-secondary-hover-background-color};
80
+ --button-secondary-active-background-color: #{$button-secondary-active-background-color};
81
+ --button-secondary-color: #{$button-secondary-color};
82
+ // danger
83
+ --button-danger-background-color: #{$button-danger-background-color};
84
+ --button-danger-hover-background-color: #{$button-danger-hover-background-color};
85
+ --button-danger-active-background-color: #{$button-danger-active-background-color};
86
+ --button-danger-color: #{$button-danger-color};
87
+ // cta
88
+ --button-cta-background-color: #{$button-cta-background-color};
89
+ --button-cta-hover-background-color: #{$button-cta-hover-background-color};
90
+ --button-cta-active-background-color: #{$button-cta-active-background-color};
91
+ --button-cta-color: #{$button-cta-color};
92
+ // link
93
+ --button-link-background-color: #{$button-link-background-color};
94
+ --button-link-hover-background-color: #{$button-link-hover-background-color};
95
+ --button-link-active-background-color: #{$button-link-active-background-color};
96
+ --button-link-color: #{$button-link-color};
97
+ // disabled
98
+ --button-disabled-background-color: #{$button-disabled-background-color};
99
+ --button-disabled-hover-background-color: #{$button-disabled-hover-background-color};
100
+ --button-disabled-active-background-color: #{$button-disabled-active-background-color};
101
+ --button-disabled-color: #{$button-disabled-color};
102
+ }
103
+
104
+ @mixin button-dark-custom-properties() {
105
+ // primary
106
+ --button-primary-background-color: #{$button-dark-primary-background-color};
107
+ --button-primary-color: #{$button-dark-primary-color};
108
+ --button-primary-hover-background-color: #{$button-dark-primary-hover-background-color};
109
+ --button-primary-active-background-color: #{$button-dark-primary-active-background-color};
110
+ // secondary
111
+ --button-secondary-background-color: #{$button-dark-secondary-background-color};
112
+ --button-secondary-color: #{$button-dark-secondary-color};
113
+ --button-secondary-hover-background-color: #{$button-dark-secondary-hover-background-color};
114
+ --button-secondary-active-background-color: #{$button-dark-secondary-active-background-color};
115
+ // cta
116
+ --button-cta-background-color: #{$button-dark-cta-background-color};
117
+ --button-cta-hover-background-color: #{$button-dark-cta-hover-background-color};
118
+ --button-cta-active-background-color: #{$button-dark-cta-active-background-color};
119
+ --button-cta-color: #{$button-dark-cta-color};
120
+ // link
121
+ --button-link-background-color: #{$button-dark-link-background-color};
122
+ --button-link-hover-background-color: #{$button-dark-link-hover-background-color};
123
+ --button-link-active-background-color: #{$button-dark-link-active-background-color};
124
+ --button-link-color: #{$button-dark-link-color};
125
+ // disabled
126
+ --button-disabled-background-color: #{$button-dark-disabled-background-color};
127
+ --button-disabled-hover-background-color: #{$button-dark-disabled-hover-background-color};
128
+ --button-disabled-active-background-color: #{$button-dark-disabled-active-background-color};
129
+ --button-disabled-color: #{$button-dark-disabled-color};
130
+ }
131
+
132
+
app/assets/js/ui/molecules/buttons.scss ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "buttons-api";
2
+
3
+ .#{$eps-prefix}button {
4
+ display: inline-flex;
5
+ //custom properties
6
+ @include button-custom-properties;
7
+ // definitions
8
+ color: var(--button-background-color, currentColor);
9
+ font-size: var(--button-font-size, inherit);
10
+ font-weight: $button-font-weight;
11
+ line-height: var(--button-line-height);
12
+ cursor: pointer;
13
+
14
+ &:active {
15
+ --button-background-color: var(--button-active-background-color, transparent);
16
+ }
17
+
18
+ &:hover {
19
+ --button-background-color: var(--button-hover-background-color);
20
+ }
21
+
22
+ .eps-theme-dark & {
23
+ //dark custom properties
24
+ @include button-dark-custom-properties;
25
+ }
26
+
27
+ // Variants
28
+
29
+ &--contained {
30
+ // definitions
31
+ color: var(--button-color);
32
+ padding: var(--button-padding-y) var(--button-padding-x);
33
+ background-color: var(--button-background-color, transparent);
34
+ border: $eps-border-width $eps-border-style var(--button-background-color);
35
+
36
+ &:hover {
37
+ color: var(--button-color);
38
+ }
39
+ }
40
+
41
+ &--outlined {
42
+ display: block;
43
+ padding: var(--button-padding-y) var(--button-padding-x);
44
+ border: $eps-border-width $eps-border-style var(--button-background-color);
45
+ }
46
+
47
+ &--contained,
48
+ &--outlined {
49
+ border-radius: $button-border-radius;
50
+ }
51
+
52
+ &--underlined {
53
+ text-decoration: underline;
54
+ }
55
+
56
+ // Sizes
57
+ &--sm {
58
+ --button-font-size: #{$button-sm-font-size};
59
+ --button-line-height: #{$button-sm-line-height};
60
+ }
61
+
62
+ &--lg {
63
+ --button-font-size: #{$button-lg-font-size};
64
+ --button-line-height: #{$button-lg-line-height};
65
+ }
66
+
67
+ // Colors
68
+
69
+ &--primary {
70
+ --button-color: var(--button-primary-color);
71
+ --button-background-color: var(--button-primary-background-color);
72
+ --button-hover-background-color: var(--button-primary-hover-background-color);
73
+ --button-active-background-color: var(--button-primary-active-background-color);
74
+ }
75
+
76
+ &--secondary {
77
+ --button-color: var(--button-secondary-color);
78
+ --button-background-color: var(--button-secondary-background-color);
79
+ --button-hover-background-color: var(--button-secondary-hover-background-color);
80
+ --button-active-background-color: var(--button-secondary-active-background-color);
81
+ }
82
+
83
+ &--danger {
84
+ --button-color: var(--button-danger-color);
85
+ --button-background-color: var(--button-danger-background-color);
86
+ --button-hover-background-color: var(--button-danger-hover-background-color);
87
+ --button-active-background-color: var(--button-danger-active-background-color);
88
+ }
89
+
90
+ &--cta {
91
+ --button-color: var(--button-cta-color);
92
+ --button-background-color: var(--button-cta-background-color);
93
+ --button-hover-background-color: var(--button-cta-hover-background-color);
94
+ --button-active-background-color: var(--button-cta-active-background-color);
95
+ }
96
+
97
+ &--link {
98
+ --button-color: var(--button-link-color);
99
+ --button-background-color: var(--button-link-background-color);
100
+ --button-hover-background-color: var(--button-link-hover-background-color);
101
+ --button-active-background-color: var(--button-link-active-background-color);
102
+ }
103
+
104
+ &--disabled,
105
+ &[disabled] {
106
+ --button-color: var(--button-disabled-color);
107
+ --button-background-color: var(--button-disabled-background-color);
108
+ --button-hover-background-color: var(--button-disabled-hover-background-color);
109
+ --button-active-background-color: var(--button-disabled-active-background-color);
110
+ cursor: default;
111
+ }
112
+ }
app/assets/js/ui/molecules/inline-link.js ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Link, LocationProvider } from '@reach/router';
2
+ import router from '@elementor/router';
3
+
4
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
5
+
6
+ import './inline-link.scss';
7
+
8
+ export default function InlineLink( props ) {
9
+ const baseClassName = 'eps-inline-link',
10
+ colorClassName = `${ baseClassName }--color-${ props.color }`,
11
+ underlineClassName = 'none' !== props.underline ? `${ baseClassName }--underline-${ props.underline }` : '',
12
+ italicClassName = props.italic ? `${ baseClassName }--italic` : '',
13
+ classes = [
14
+ baseClassName,
15
+ colorClassName,
16
+ underlineClassName,
17
+ italicClassName,
18
+ props.className,
19
+ ],
20
+ className = arrayToClassName( classes ),
21
+ getRouterLink = () => (
22
+ <LocationProvider history={ router.appHistory }>
23
+ <Link
24
+ to={ props.url }
25
+ className={ className }
26
+ >
27
+ { props.children }
28
+ </Link>
29
+ </LocationProvider>
30
+ ),
31
+ getExternalLink = () => (
32
+ <a
33
+ href={ props.url }
34
+ target={ props.target }
35
+ rel={ props.rel }
36
+ className={ className }
37
+ onClick={ props.onClick }
38
+ >
39
+ { props.children }
40
+ </a>
41
+ ),
42
+ getActionLink = () => (
43
+ <button className={ className } onClick={ props.onClick }>
44
+ { props.children }
45
+ </button>
46
+ );
47
+
48
+ if ( ! props.url ) {
49
+ return getActionLink();
50
+ }
51
+
52
+ return props.url.includes( 'http' ) ? getExternalLink() : getRouterLink();
53
+ }
54
+
55
+ InlineLink.propTypes = {
56
+ className: PropTypes.string,
57
+ children: PropTypes.any,
58
+ url: PropTypes.string,
59
+ target: PropTypes.string,
60
+ rel: PropTypes.string,
61
+ text: PropTypes.string,
62
+ color: PropTypes.oneOf( [ 'primary', 'secondary', 'cta', 'link', 'disabled' ] ),
63
+ underline: PropTypes.oneOf( [ 'none', 'hover', 'always' ] ),
64
+ italic: PropTypes.bool,
65
+ onClick: PropTypes.func,
66
+ };
67
+
68
+ InlineLink.defaultProps = {
69
+ className: '',
70
+ color: 'link',
71
+ underline: 'always',
72
+ target: '_blank',
73
+ rel: 'noopener noreferrer',
74
+ };
app/assets/js/ui/molecules/inline-link.scss ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-inline-link-color-primary: theme-colors(success);
2
+ $eps-inline-link-color-secondary: tints(400);
3
+ $eps-inline-link-color-danger: theme-colors(danger);
4
+ $eps-inline-link-color-cta: theme-colors(cta);
5
+ $eps-inline-link-color-link: theme-elements-colors(link-color);
6
+ $eps-inline-link-color-disabled: theme-colors(disabled);
7
+
8
+ .eps-inline-link {
9
+ color: var(--eps-inline-link-color);
10
+
11
+ &--color {
12
+ &-primary {
13
+ --eps-inline-link-color: #{$eps-inline-link-color-primary};
14
+ }
15
+
16
+ &-secondary {
17
+ --eps-inline-link-color: #{$eps-inline-link-color-secondary};
18
+ }
19
+
20
+ &-danger {
21
+ --eps-inline-link-color: #{$eps-inline-link-color-danger};
22
+ }
23
+
24
+ &-cta {
25
+ --eps-inline-link-color: #{$eps-inline-link-color-cta};
26
+ }
27
+
28
+ &-link {
29
+ --eps-inline-link-color: #{$eps-inline-link-color-link};
30
+ }
31
+
32
+ &-disabled {
33
+ --eps-inline-link-color: #{$eps-inline-link-color-disabled};
34
+ }
35
+ }
36
+
37
+ &--underline {
38
+ &-hover:hover,
39
+ &-always,
40
+ &-always:hover {
41
+ text-decoration: underline;
42
+ }
43
+ }
44
+
45
+ &--italic {
46
+ font-style: italic;
47
+ }
48
+
49
+ // Reset style for button tag.
50
+ background-color: initial;
51
+ border: 0;
52
+ padding: 0;
53
+
54
+ &,
55
+ &:focus {
56
+ outline: none;
57
+ }
58
+ }
app/assets/js/ui/molecules/list-item.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { pxToRem, arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ export default function ListItem( props ) {
4
+ const baseClassName = 'eps-list__item',
5
+ classes = [ baseClassName, props.className ];
6
+
7
+ let style;
8
+
9
+ if ( Object.prototype.hasOwnProperty.call( props, 'padding' ) ) {
10
+ style = {
11
+ '--eps-list-item-padding': pxToRem( props.padding ),
12
+ };
13
+
14
+ classes.push( baseClassName + '--padding' );
15
+ }
16
+
17
+ return (
18
+ <li style={ style } className={ arrayToClassName( classes ) }>
19
+ { props.children }
20
+ </li>
21
+ );
22
+ }
23
+
24
+ ListItem.propTypes = {
25
+ className: PropTypes.string,
26
+ padding: PropTypes.string,
27
+ children: PropTypes.any.isRequired,
28
+ };
29
+
30
+ ListItem.defaultProps = {
31
+ className: '',
32
+ };
app/assets/js/ui/molecules/list.js ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { pxToRem, arrayToClassName } from 'elementor-app/utils/utils.js';
2
+ import ListItem from './list-item';
3
+
4
+ import './list.scss';
5
+
6
+ export default function List( props ) {
7
+ const baseClassName = 'eps-list',
8
+ classes = [ baseClassName, props.className ];
9
+
10
+ let style;
11
+
12
+ if ( Object.prototype.hasOwnProperty.call( props, 'padding' ) ) {
13
+ style = {
14
+ '--eps-list-padding': pxToRem( props.padding ),
15
+ };
16
+
17
+ classes.push( baseClassName + '--padding' );
18
+ }
19
+
20
+ if ( props.separated ) {
21
+ classes.push( baseClassName + '--separated' );
22
+ }
23
+
24
+ return (
25
+ <ul style={ style } className={ arrayToClassName( classes ) }>
26
+ { props.children }
27
+ </ul>
28
+ );
29
+ }
30
+
31
+ List.propTypes = {
32
+ className: PropTypes.string,
33
+ divided: PropTypes.any,
34
+ separated: PropTypes.any,
35
+ padding: PropTypes.string,
36
+ children: PropTypes.oneOfType( [
37
+ PropTypes.object,
38
+ PropTypes.arrayOf( PropTypes.object ),
39
+ ] ).isRequired,
40
+ };
41
+
42
+ List.defaultProps = {
43
+ className: '',
44
+ };
45
+
46
+ List.Item = ListItem;
app/assets/js/ui/molecules/list.scss ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-list-item-separated-border-color: tints(200);
2
+ $eps-list-item-separated-dark-border-color: dark-tints(700);
3
+
4
+ $root: eps-list;
5
+
6
+ .#{$root} {
7
+ --eps-list-item-separated-border-color: #{$eps-list-item-separated-border-color};
8
+
9
+ padding: 0;
10
+ margin: 0;
11
+ list-style-type: none;
12
+
13
+ &--padding {
14
+ padding: var(--eps-list-padding);
15
+ }
16
+
17
+ &__item {
18
+ padding: 0;
19
+
20
+ &--padding {
21
+ padding: var(--eps-list-item-padding);
22
+ }
23
+ }
24
+
25
+ &--separated {
26
+ .#{$root}__item:not(:last-child) {
27
+ border-bottom: 1px solid var(--eps-list-item-separated-border-color);
28
+ }
29
+ }
30
+ }
31
+
32
+ .eps-theme-dark {
33
+ .#{$root} {
34
+ --eps-list-item-separated-border-color: #{$eps-list-item-separated-dark-border-color};
35
+ }
36
+ }
app/assets/js/ui/molecules/notice.js ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import Text from 'elementor-app/ui/atoms/text';
4
+ import Icon from 'elementor-app/ui/atoms/icon';
5
+ import Grid from 'elementor-app/ui/grid/grid';
6
+
7
+ import './notice.scss';
8
+
9
+ const iconsClassesMap = {
10
+ danger: 'eicon-warning',
11
+ info: 'eicon-info-circle-o',
12
+ warning: 'eicon-warning',
13
+ };
14
+
15
+ export default function Notice( props ) {
16
+ const baseClassName = 'eps-notice',
17
+ classes = [ baseClassName, props.className ];
18
+
19
+ if ( props.color ) {
20
+ classes.push( baseClassName + '-semantic', baseClassName + '--' + props.color );
21
+ }
22
+
23
+ return (
24
+ <Grid className={ arrayToClassName( classes ) } container noWrap alignItems="center" justify="space-between">
25
+ <Grid item container alignItems="start" noWrap>
26
+ {
27
+ props.withIcon &&
28
+ props.color &&
29
+ <Icon className={ arrayToClassName( [ 'eps-notice__icon', iconsClassesMap[ props.color ] ] ) } />
30
+ }
31
+
32
+ <Text variant="xs" className="eps-notice__text">
33
+ { props.label && <strong>{ props.label + ' ' }</strong> }
34
+
35
+ { props.children }
36
+ </Text>
37
+ </Grid>
38
+
39
+ {
40
+ props.button &&
41
+ <Grid item container justify="end" className={ baseClassName + '__button-container' }>
42
+ { props.button }
43
+ </Grid>
44
+ }
45
+ </Grid>
46
+ );
47
+ }
48
+
49
+ Notice.propTypes = {
50
+ className: PropTypes.string,
51
+ color: PropTypes.string,
52
+ label: PropTypes.string,
53
+ children: PropTypes.any.isRequired,
54
+ icon: PropTypes.string,
55
+ withIcon: PropTypes.bool,
56
+ button: PropTypes.object,
57
+ };
58
+
59
+ Notice.defaultProps = {
60
+ className: '',
61
+ withIcon: true,
62
+ button: null,
63
+ };
app/assets/js/ui/molecules/notice.scss ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-box-notice-color: tints(500);
2
+ $eps-box-notice-dark-color: dark-tints(200);
3
+ $eps-box-notice-background-color: tints(100);
4
+ $eps-box-notice-dark-background-color: dark-tints(600);
5
+
6
+ $eps-notice-semantic-warning-color: theme-colors(warning);
7
+ $eps-notice-semantic-danger-color: theme-colors(danger);
8
+ $eps-notice-semantic-info-color: theme-colors(info);
9
+
10
+ $root: eps-notice;
11
+
12
+ .#{$root} {
13
+ $parent: &;
14
+
15
+ --eps-box-notice-color: #{$eps-box-notice-color};
16
+ --eps-box-notice-background-color: #{$eps-box-notice-background-color};
17
+
18
+ padding: spacing(10) spacing(16);
19
+ box-shadow: $eps-box-shadow-1;
20
+ background-color: var(--eps-box-notice-background-color);
21
+
22
+ &-semantic {
23
+ border-inline-start: $eps-border-width-lg $border-style-solid var(--eps-notice-semantic-color);
24
+
25
+ #{$parent}__icon {
26
+ color: var(--eps-notice-semantic-color);
27
+ font-size: type(text,xl);
28
+ margin-inline-end: spacing(12);
29
+ }
30
+ }
31
+
32
+ &--warning {
33
+ --eps-notice-semantic-color: #{$eps-notice-semantic-warning-color};
34
+ }
35
+
36
+ &--danger {
37
+ --eps-notice-semantic-color: #{$eps-notice-semantic-danger-color};
38
+ }
39
+
40
+ &--info {
41
+ --eps-notice-semantic-color: #{$eps-notice-semantic-info-color};
42
+ }
43
+
44
+ &__text {
45
+ margin: 0;
46
+ padding: 0;
47
+ color: var(--eps-box-notice-color);
48
+ font-style: italic;
49
+ }
50
+
51
+ &__button-container {
52
+ flex-shrink: 0;
53
+ margin-left: spacing(20);
54
+ width: auto;
55
+ }
56
+ }
57
+
58
+ .eps-theme-dark {
59
+ .#{$root} {
60
+ --eps-box-notice-color: #{$eps-box-notice-dark-color};
61
+ --eps-box-notice-background-color: #{$eps-box-notice-dark-background-color};
62
+ }
63
+ }
app/assets/js/ui/molecules/popover-api.scss ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // POPOVER: API
2
+ $eps-popover-padding-y: 10px;
3
+ $eps-popover-background-color: theme-colors(light);
4
+ $eps-popover-border-radius: $eps-radius;
5
+ $eps-popover-arrow-color: theme-colors(light);
6
+ $eps-popover-arrow-width: 16px;
7
+ $eps-popover-arrow-height: 9px;
8
+ $eps-popover-arrow-position-start: 14px;
9
+ $eps-popover-box-shadow-color: rgba(0, 0, 0, 0.15);
10
+ $eps-popover-box-shadow-size: 0px 1px 20px; // add to globals
11
+ $eps-popover-item-font-size: type(size, "11");
12
+ $eps-popover-item-line-height: type(size, "13"); //todo: refactor
13
+ $eps-popover-font-weight: $eps-font-weight-medium;
14
+ $eps-popover-item-spacing-y: spacing(5);
15
+ $eps-popover-item-spacing-x: spacing(16);
16
+ $eps-popover-item-background-color: theme-colors(light);
17
+ $eps-popover-item-color: tints(600);
18
+ $eps-popover-item-hover-color: tints(700);
19
+ $eps-popover-item-danger-hover-color: theme-colors(danger);
20
+
21
+ // dark theme
22
+ $eps-dark-popover-background-color: dark-tints(500);
23
+ $eps-dark-popover-item-color: theme-colors(light);
24
+ $eps-dark-popover-item-hover-color: dark-tints(100);
25
+ $eps-dark-popover-item-danger-hover-color: dark-theme-colors(danger);
26
+ $eps-dark-popover-item-background-color: $eps-dark-popover-background-color;
27
+ $eps-dark-popover-box-shadow-color: rgba(0, 0, 0, 0.15);
28
+ $eps-dark-popover-box-shadow-size: 0px 1px 20px;
29
+ $eps-dark-popover-arrow-color: $eps-dark-popover-background-color;
30
+
31
+ :root {
32
+ --popover-background-color: #{$eps-popover-background-color};
33
+ --popover-item-color: #{$eps-popover-item-color};
34
+ --popover-item-hover-color: #{$eps-popover-item-hover-color};
35
+ --popover-item-danger-hover-color: #{$eps-popover-item-danger-hover-color};
36
+ --popover-item-background-color: #{$eps-popover-item-background-color};
37
+ --popover-box-shadow-color: #{$eps-popover-box-shadow-color};
38
+ --popover-box-shadow-size: #{$eps-popover-box-shadow-size};
39
+ --popover-arrow-color: #{$eps-popover-arrow-color};
40
+ }
41
+
42
+ .eps-theme-dark {
43
+ --popover-background-color: #{$eps-dark-popover-background-color};
44
+ --popover-item-color: #{$eps-dark-popover-item-color};
45
+ --popover-item-hover-color: #{$eps-dark-popover-item-hover-color};
46
+ --popover-item-danger-hover-color: #{$eps-dark-popover-item-danger-hover-color};
47
+ --popover-item-background-color: #{$eps-dark-popover-item-background-color};
48
+ --popover-box-shadow-color: #{$eps-dark-popover-box-shadow-color};
49
+ --popover-box-shadow-size: #{$eps-dark-popover-box-shadow-size};
50
+ --popover-arrow-color: #{$eps-dark-popover-arrow-color};
51
+ }
app/assets/js/ui/molecules/popover.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
2
+ /* eslint-disable jsx-a11y/no-static-element-interactions */
3
+ /* eslint-disable jsx-a11y/click-events-have-key-events */
4
+ import './popover.scss';
5
+
6
+ export default function Popover( props ) {
7
+ return (
8
+ <>
9
+ <div className="eps-popover__background" onClick={ props.closeFunction } />
10
+ <ul className={ `eps-popover ${ props.className }` } onClick={ props.closeFunction }>
11
+ { props.children }
12
+ </ul>
13
+ </>
14
+ );
15
+ }
16
+
17
+ Popover.propTypes = {
18
+ children: PropTypes.any.isRequired,
19
+ className: PropTypes.string,
20
+ closeFunction: PropTypes.func,
21
+ };
22
+
23
+ Popover.defaultProps = {
24
+ className: '',
25
+ };
app/assets/js/ui/molecules/popover.scss ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "popover-api";
2
+
3
+ // checkbox label
4
+ .#{$eps-prefix}popover {
5
+ padding: $eps-popover-padding-y 0;
6
+ background-color: var(--popover-background-color);
7
+ box-shadow: var(--popover-box-shadow-size) var(--popover-box-shadow-color);
8
+ list-style: none;
9
+ display: flex;
10
+ flex-direction: column;
11
+ min-width: 120px;
12
+ border-radius: $eps-popover-border-radius;
13
+ position: absolute;
14
+ z-index: $eps-zindex-popover;
15
+ margin-top: $eps-popover-arrow-height;
16
+ transform: translateX(-50%);
17
+ left: px-to-rem(4);
18
+
19
+ &__background {
20
+ position: fixed;
21
+ top: 0;
22
+ bottom: 0;
23
+ left: 0;
24
+ right: 0;
25
+ z-index: $eps-zindex-modal-backdrop;
26
+ }
27
+
28
+ &__container {
29
+ position: relative;
30
+ }
31
+
32
+ &::before {
33
+ content: "";
34
+ display: block;
35
+ position: absolute;
36
+ width: $eps-popover-arrow-width;
37
+ height: $eps-popover-arrow-height;
38
+ margin: 0 $eps-popover-border-radius $eps-popover-arrow-height;
39
+ top: -$eps-popover-arrow-height;
40
+ left: 50%;
41
+ transform: translateX(-50%);
42
+ border-color: transparent;
43
+ border-style: solid;
44
+ border-width: 0 calc(#{$eps-popover-arrow-width} / 2) $eps-popover-arrow-height calc(#{$eps-popover-arrow-width} / 2);
45
+ border-bottom-color: var(--popover-arrow-color);
46
+ }
47
+
48
+ &__item {
49
+ padding: $eps-popover-item-spacing-y $eps-popover-item-spacing-x;
50
+ background-color: var(--popover-item-background-color);
51
+ color: var(--popover-item-color);
52
+ font-size: $eps-popover-item-font-size;
53
+ font-weight: $eps-popover-font-weight;
54
+ line-height: $eps-popover-item-line-height;
55
+ width: 100%;
56
+ align-items: center;
57
+ cursor: pointer;
58
+
59
+ &:hover {
60
+ color: var(--popover-item-hover-color);
61
+ }
62
+
63
+ &--danger {
64
+ &:hover {
65
+ color: var(--popover-item-danger-hover-color);
66
+ }
67
+ }
68
+
69
+ .#{$eps-prefix}icon {
70
+ font-size: inherit;
71
+ margin-inline-end: spacing(5);
72
+ }
73
+ }
74
+ }
app/assets/js/ui/molecules/select2-api.scss ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $select-box-selected-item-default-color: #5897fb;
2
+
3
+ // Selection Box
4
+ $select2-selection-background-color: theme-colors(light);
5
+ $select2-selection-dark-background-color: dark-tints(700);
6
+
7
+ $select2-selection-color: tints(600);
8
+ $select2-selection-dark-color: dark-tints(100);
9
+
10
+ $select2-selection-border-color: tints(300);
11
+ $select2-selection-dark-border-color: dark-tints(400);
12
+
13
+ $select2-selection-opened-focused-border-color: tints(200);
14
+ $select2-selection-opened-focused-dark-border-color: dark-tints(300);
15
+
16
+ $select2-single-selection-rendered-color: tints(600);
17
+ $select2-single-selection-rendered-dark-color: dark-tints(100);
18
+
19
+ $select2-default-single-selection-background-color: theme-colors(light);
20
+ $select2-default-single-selection-dark-background-color: dark-tints(700);
21
+
22
+ $select2-default-single-selection-border-color: tints(300);
23
+ $select2-default-single-selection-dark-border-color: dark-tints(500);
24
+
25
+ $select2-default-multiple-selection-background-color: theme-colors(light);
26
+ $select2-default-multiple-selection-dark-background-color: dark-tints(700);
27
+
28
+ $select2-default-multiple-selection-choice-background-color: tints(200);
29
+ $select2-default-multiple-selection-choice-dark-background-color: dark-tints(500);
30
+
31
+ $select2-default-multiple-selection-choice-color: tints(600);
32
+ $select2-default-multiple-selection-choice-dark-color: dark-tints(100);
33
+
34
+ $select2-default-multiple-selection-choice-border-color: tints(200);
35
+ $select2-default-multiple-selection-choice-dark-border-color: dark-tints(500);
36
+
37
+ $select2-default-multiple-selection-choice-remove-color: tints(500);
38
+ $select2-default-multiple-selection-choice-remove-dark-color: dark-tints(200);
39
+
40
+ $select2-default-multiple-selection-choice-remove-hover-color: tints(600);
41
+ $select2-default-multiple-selection-choice-remove-hover-dark-color: dark-tints(100);
42
+
43
+ // Option
44
+
45
+ $select2-default-results-selected-option-background-color: theme-colors(light);
46
+ $select2-default-results-selected-option-dark-background-color: dark-tints(700);
47
+
48
+ $select2-default-results-selected-option-color: tints(600);
49
+ $select2-default-results-selected-option-dark-color: dark-tints(100);
50
+
51
+ // Highlighted Option
52
+
53
+ $select2-default-results-highlighted-option-background-color: $select-box-selected-item-default-color;
54
+ $select2-default-results-highlighted-option-dark-background-color: dark-tints(500);
55
+
56
+ $select2-default-results-highlighted-option-color: theme-colors(light);
57
+ $select2-default-results-highlighted-option-dark-color: dark-tints(100);
58
+
59
+ // Results - Selected
60
+
61
+ $select2-results-selected-option-background-color: $select-box-selected-item-default-color;
62
+ $select2-results-selected-option-dark-background-color: dark-tints(500);
63
+
64
+ $select2-results-selected-option-color: theme-colors(light);
65
+ $select2-results-selected-option-dark-color: dark-tints(100);
66
+
67
+ // Dropdown
68
+
69
+ $select2-dropdown-background-color: theme-colors(light);
70
+ $select2-dropdown-dark-background-color: dark-tints(700);
71
+
72
+ $select2-dropdown-border-color: tints(300);
73
+ $select2-dropdown-dark-border-color: dark-tints(400);
74
+
75
+ :root {
76
+ // Selection Box
77
+ --select2-selection-background-color: #{$select2-selection-background-color};
78
+ --select2-selection-color: #{$select2-selection-color};
79
+ --select2-selection-border-color: #{$select2-selection-border-color};
80
+ --select2-selection-opened-focused-border-color: #{$select2-selection-opened-focused-border-color};
81
+
82
+ --select2-single-selection-rendered-color: #{$select2-single-selection-rendered-color};
83
+
84
+ --select2-default-single-selection-background-color: #{$select2-default-single-selection-background-color};
85
+
86
+ --select2-default-single-selection-border-color: #{$select2-default-single-selection-border-color};
87
+
88
+ --select2-default-multiple-selection-background-color: #{$select2-default-multiple-selection-background-color};
89
+
90
+ // Multiple - selected items
91
+ --select2-default-multiple-selection-choice-background-color: #{$select2-default-multiple-selection-choice-background-color};
92
+ --select2-default-multiple-selection-choice-color: #{$select2-default-multiple-selection-choice-color};
93
+ --select2-default-multiple-selection-choice-border-color: #{$select2-default-multiple-selection-choice-border-color};
94
+ --select2-default-multiple-selection-choice-remove-color: #{$select2-default-multiple-selection-choice-remove-color};
95
+ --select2-default-multiple-selection-choice-remove-hover-color: #{$select2-default-multiple-selection-choice-remove-hover-color};
96
+
97
+ // Option
98
+ --select2-default-results-selected-option-background-color: #{$select2-default-results-selected-option-background-color};
99
+ --select2-default-results-selected-option-color: #{$select2-default-results-selected-option-color};
100
+
101
+ // Highlighted Option
102
+ --select2-default-results-highlighted-option-background-color: #{$select2-default-results-highlighted-option-background-color};
103
+ --select2-default-results-highlighted-option-color: #{$select2-default-results-highlighted-option-color};
104
+
105
+ // Results - Selected
106
+ --select2-results-selected-option-background-color: #{$select2-results-selected-option-background-color};
107
+ --select2-results-selected-option-color: #{$select2-results-selected-option-color};
108
+
109
+ // Dropdown
110
+ --select2-dropdown-background-color: #{$select2-dropdown-background-color};
111
+ --select2-dropdown-border-color: #{$select2-dropdown-border-color};
112
+ }
113
+
114
+ .eps-theme-dark {
115
+ // Selection Box
116
+ --select2-selection-background-color: #{$select2-selection-dark-background-color};
117
+ --select2-selection-color: #{$select2-selection-dark-color};
118
+ --select2-selection-border-color: #{$select2-selection-dark-border-color};
119
+ --select2-selection-opened-focused-border-color: #{$select2-selection-opened-focused-dark-border-color};
120
+
121
+ --select2-single-selection-rendered-color: #{$select2-single-selection-rendered-dark-color};
122
+
123
+ --select2-default-single-selection-background-color: #{$select2-default-single-selection-dark-background-color};
124
+
125
+ --select2-default-single-selection-border-color: #{$select2-default-single-selection-dark-border-color};
126
+
127
+ --select2-default-multiple-selection-background-color: #{$select2-default-multiple-selection-dark-background-color};
128
+
129
+ // Multiple - Selected Items
130
+ --select2-default-multiple-selection-choice-background-color: #{$select2-default-multiple-selection-choice-dark-background-color};
131
+ --select2-default-multiple-selection-choice-color: #{$select2-default-multiple-selection-choice-dark-color};
132
+ --select2-default-multiple-selection-choice-border-color: #{$select2-default-multiple-selection-choice-dark-border-color};
133
+ --select2-default-multiple-selection-choice-remove-color: #{$select2-default-multiple-selection-choice-remove-dark-color};
134
+ --select2-default-multiple-selection-choice-remove-hover-color: #{$select2-default-multiple-selection-choice-remove-hover-dark-color};
135
+
136
+ // Option
137
+ --select2-default-results-selected-option-background-color: #{$select2-default-results-selected-option-dark-background-color};
138
+ --select2-default-results-selected-option-color: #{$select2-default-results-selected-option-dark-color};
139
+
140
+ // Highlighted Option
141
+ --select2-default-results-highlighted-option-background-color: #{$select2-default-results-highlighted-option-dark-background-color};
142
+ --select2-default-results-highlighted-option-color: #{$select2-default-results-highlighted-option-dark-color};
143
+
144
+ // Results - Selected
145
+ --select2-results-selected-option-background-color: #{$select2-results-selected-option-dark-background-color};
146
+ --select2-results-selected-option-color: #{$select2-results-selected-option-dark-color};
147
+
148
+ // Dropdown
149
+ --select2-dropdown-background-color: #{$select2-dropdown-dark-background-color};
150
+ --select2-dropdown-border-color: #{$select2-dropdown-dark-border-color};
151
+ }
app/assets/js/ui/molecules/select2.js ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Select from '../atoms/select';
2
+
3
+ import './select2.scss';
4
+
5
+ /**
6
+ * Default settings of the select 2
7
+ *
8
+ * @return {{placeholder: string, allowClear: boolean, dir: string}}
9
+ */
10
+
11
+ const getDefaultSettings = () => ( {
12
+ allowClear: true,
13
+ placeholder: '',
14
+ dir: elementorCommon.config.isRTL ? 'rtl' : 'ltr',
15
+ } );
16
+ /**
17
+ * Main component
18
+ *
19
+ * @param {*} props
20
+ * @return {*} component
21
+ * @function Object() { [native code] }
22
+ */
23
+ export default function Select2( props ) {
24
+ const ref = React.useRef( null );
25
+
26
+ // Initiate the select 2 library, call to onReady after initiate, and
27
+ // listen to select event on the select instance.
28
+ React.useEffect( () => {
29
+ const $select2 = jQuery( ref.current )
30
+ .select2( {
31
+ ...getDefaultSettings(),
32
+ ...props.settings,
33
+ placeholder: props.placeholder,
34
+ } )
35
+ .on( 'select2:select select2:unselect', props.onChange );
36
+
37
+ if ( props.onReady ) {
38
+ props.onReady( $select2 );
39
+ }
40
+
41
+ return () => {
42
+ $select2.select2( 'destroy' ).off( 'select2:select select2:unselect' );
43
+ };
44
+ }, [ props.settings, props.options ] );
45
+
46
+ // Listen to changes in the prop `value`, if changed update the select 2.
47
+ React.useEffect( () => {
48
+ jQuery( ref.current ).val( props.value ).trigger( 'change' );
49
+ }, [ props.value ] );
50
+
51
+ return <Select
52
+ multiple={ props.multiple }
53
+ value={ props.value }
54
+ onChange={ props.onChange }
55
+ elRef={ ref }
56
+ options={ props.options }
57
+ placeholder={ props.placeholder }
58
+ />;
59
+ }
60
+ Select2.propTypes = {
61
+ value: PropTypes.oneOfType( [ PropTypes.array, PropTypes.string ] ),
62
+ onChange: PropTypes.func,
63
+ onReady: PropTypes.func,
64
+ options: PropTypes.array,
65
+ settings: PropTypes.object,
66
+ multiple: PropTypes.bool,
67
+ placeholder: PropTypes.string,
68
+ };
69
+ Select2.defaultProps = {
70
+ settings: {},
71
+ options: [],
72
+ dependencies: [],
73
+ placeholder: '',
74
+ };
app/assets/js/ui/molecules/select2.scss ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import 'select2-api';
2
+
3
+ .select2-container {
4
+ &:not(.select2-container--open):not(.select2-container--focus) {
5
+
6
+ .select2-selection--single,
7
+ .select2-selection--multiple {
8
+ background-color: var(--select2-selection-background-color);
9
+ color: var(--select2-selection-color);
10
+ border-color: var(--select2-selection-border-color);
11
+ }
12
+ }
13
+
14
+ &.select2-container--open,
15
+ &.select2-container--focus {
16
+
17
+ .select2-selection--single,
18
+ .select2-selection--multiple {
19
+ border-color: var(--select2-selection-opened-focused-border-color);
20
+ }
21
+ }
22
+
23
+ &.select2-container--default {
24
+
25
+ .select2-selection--single {
26
+
27
+ .select2-selection__rendered {
28
+ color: var(--select2-single-selection-rendered-color);
29
+ }
30
+ }
31
+ }
32
+
33
+ &--default {
34
+
35
+ .select2-selection {
36
+
37
+ &--single {
38
+ background-color: var(--select2-default-single-selection-background-color);
39
+ border-color: var(--select2-default-single-selection-border-color);
40
+ }
41
+
42
+ &--multiple {
43
+ background-color: var(--select2-default-multiple-selection-background-color);
44
+
45
+ .select2-selection__choice {
46
+ background-color: var(--select2-default-multiple-selection-choice-background-color);
47
+ color: var(--select2-default-multiple-selection-choice-color);
48
+ border-color: var(--select2-default-multiple-selection-choice-border-color);
49
+
50
+ &__remove {
51
+ color: var(--select2-default-multiple-selection-choice-remove-color);
52
+
53
+ &:hover {
54
+ color: var(--select2-default-multiple-selection-choice-remove-hover-color);
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ .select2-results {
62
+
63
+ &__option[aria-selected] {
64
+ background-color: var(--select2-default-results-selected-option-background-color);
65
+ color: var(--select2-default-results-selected-option-color);
66
+ }
67
+
68
+ &__option--highlighted[aria-selected] {
69
+ background-color: var(--select2-default-results-highlighted-option-background-color);
70
+ color: var(--select2-default-results-highlighted-option-color);
71
+ }
72
+
73
+ }
74
+ }
75
+
76
+ .select2-results__option[aria-selected=true] {
77
+ background-color: var(--select2-results-selected-option-background-color);
78
+ color: var(--select2-results-selected-option-color);
79
+ }
80
+
81
+ .select2-dropdown {
82
+ background-color: var(--select2-dropdown-background-color);
83
+ border-color: var(--select2-dropdown-border-color);
84
+ }
85
+ }
app/assets/js/ui/panel/panel-body.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import Card from 'elementor-app/ui/card/card';
4
+ import Collapse from 'elementor-app/molecules/collapse';
5
+
6
+ export default function PanelBody( props ) {
7
+ return (
8
+ <Collapse.Content>
9
+ <Card.Body padding={ props.padding } className={ arrayToClassName( [ 'eps-panel__body', props.className ] ) }>
10
+ { props.children }
11
+ </Card.Body>
12
+ </Collapse.Content>
13
+ );
14
+ }
15
+
16
+ PanelBody.propTypes = {
17
+ className: PropTypes.string,
18
+ padding: PropTypes.string,
19
+ children: PropTypes.any.isRequired,
20
+ };
21
+
22
+ PanelBody.defaultProps = {
23
+ className: '',
24
+ padding: '0',
25
+ };
app/assets/js/ui/panel/panel-header.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import Card from 'elementor-app/ui/card/card';
4
+ import Collapse from 'elementor-app/molecules/collapse';
5
+
6
+ export default function PanelHeader( props ) {
7
+ return (
8
+ <Collapse.Toggle active={ props.toggle } showIcon={ props.showIcon }>
9
+ <Card.Header padding="20" className={ arrayToClassName( [ 'eps-panel__header', props.className ] ) }>
10
+ { props.children }
11
+ </Card.Header>
12
+ </Collapse.Toggle>
13
+ );
14
+ }
15
+
16
+ PanelHeader.propTypes = {
17
+ className: PropTypes.string,
18
+ padding: PropTypes.string,
19
+ toggle: PropTypes.bool,
20
+ showIcon: PropTypes.bool,
21
+ children: PropTypes.any.isRequired,
22
+ };
23
+
24
+ PanelHeader.defaultProps = {
25
+ className: '',
26
+ padding: '20',
27
+ toggle: true,
28
+ showIcon: true,
29
+ };
app/assets/js/ui/panel/panel-headline.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import Card from 'elementor-app/ui/card/card';
4
+
5
+ export default function PanelHeadline( props ) {
6
+ return (
7
+ <Card.Headline className={ arrayToClassName( [ 'eps-panel__headline', props.className ] ) }>
8
+ { props.children }
9
+ </Card.Headline>
10
+ );
11
+ }
12
+
13
+ PanelHeadline.propTypes = {
14
+ className: PropTypes.string,
15
+ children: PropTypes.any.isRequired,
16
+ };
17
+
18
+ PanelHeadline.defaultProps = {
19
+ className: '',
20
+ };
app/assets/js/ui/panel/panel.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import Card from 'elementor-app/ui/card/card';
4
+ import Collapse from 'elementor-app/molecules/collapse';
5
+ import PanelHeader from './panel-header';
6
+ import PanelHeadline from './panel-headline';
7
+ import PanelBody from './panel-body';
8
+
9
+ import './panel.scss';
10
+
11
+ export default function Panel( props ) {
12
+ return (
13
+ <Collapse isOpened={ props.isOpened }>
14
+ <Card className={ arrayToClassName( [ 'eps-panel', props.className ] ) }>
15
+ { props.children }
16
+ </Card>
17
+ </Collapse>
18
+ );
19
+ }
20
+
21
+ Panel.propTypes = {
22
+ className: PropTypes.string,
23
+ isOpened: PropTypes.bool,
24
+ children: PropTypes.any.isRequired,
25
+ };
26
+
27
+ Panel.defaultProps = {
28
+ className: '',
29
+ isOpened: false,
30
+ };
31
+
32
+ Panel.Header = PanelHeader;
33
+ Panel.Headline = PanelHeadline;
34
+ Panel.Body = PanelBody;
app/assets/js/ui/panel/panel.scss ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-panel-header-background-color: theme-colors(light);
2
+ $eps-panel-header-dark-background-color: dark-tints(600);
3
+ $eps-panel-body-background-color: rgba(theme-colors(light), $opacity-05);
4
+ $eps-panel-body-dark-background-color: rgba(dark-tints(600), $opacity-05);
5
+
6
+ $root: eps-panel;
7
+
8
+ .#{$root} {
9
+ --eps-panel-header-background-color: #{$eps-panel-header-background-color};
10
+ --eps-panel-body-background-color: #{$eps-panel-body-background-color};
11
+
12
+ &,
13
+ &:hover {
14
+ background-color: initial;
15
+ }
16
+
17
+ &__header {
18
+ background-color: var(--eps-panel-header-background-color);
19
+ border-radius: $eps-radius;
20
+ }
21
+
22
+ &__body {
23
+ background-color: var(--eps-panel-body-background-color);
24
+ border-radius: 0 0 $eps-radius $eps-radius;
25
+ }
26
+ }
27
+
28
+ .eps-theme-dark {
29
+ .#{$root} {
30
+ --eps-panel-header-background-color: #{$eps-panel-header-dark-background-color};
31
+ --eps-panel-body-background-color: #{$eps-panel-body-dark-background-color};
32
+ }
33
+ }
app/assets/js/ui/popover-dialog/popover-dialog.js ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useCallback } from 'react';
2
+
3
+ export default function PopoverDialog( props ) {
4
+ const { targetRef, offsetTop, offsetLeft, wrapperClass, trigger, hideAfter } = props,
5
+ popoverRef = useCallback( ( popoverEl ) => {
6
+ const target = targetRef?.current;
7
+
8
+ // If the target or the popover element does not exist on the page anymore after a re-render, do nothing.
9
+ if ( ! target || ! popoverEl ) {
10
+ return;
11
+ }
12
+
13
+ /**
14
+ * Show Popover
15
+ */
16
+ const showPopover = () => {
17
+ popoverEl.style.display = 'block';
18
+
19
+ popoverEl.setAttribute( 'aria-expanded', true );
20
+
21
+ const targetRect = target.getBoundingClientRect(),
22
+ popoverRect = popoverEl.getBoundingClientRect(),
23
+ widthDifference = popoverRect.width - targetRect.width;
24
+
25
+ popoverEl.style.top = targetRect.bottom + offsetTop + 'px';
26
+ popoverEl.style.left = targetRect.left - ( widthDifference / 2 ) - offsetLeft + 'px';
27
+
28
+ // 16px to compensate for the arrow width.
29
+ popoverEl.style.setProperty( '--popover-arrow-offset-end', ( ( popoverRect.width - 16 ) / 2 ) + 'px' );
30
+ };
31
+
32
+ /**
33
+ * Hide Popover
34
+ */
35
+ const hidePopover = () => {
36
+ popoverEl.style.display = 'none';
37
+ popoverEl.setAttribute( 'aria-expanded', false );
38
+ };
39
+
40
+ /**
41
+ * Handle the Popover's hover functionality
42
+ */
43
+ const handlePopoverHover = () => {
44
+ let hideOnMouseOut = true,
45
+ timeOut = null;
46
+
47
+ // Show popover on hover of the target
48
+ target.addEventListener( 'mouseover', () => {
49
+ hideOnMouseOut = true;
50
+
51
+ showPopover();
52
+ } );
53
+
54
+ // Hide popover when not overing over the target or the popover itself
55
+ target.addEventListener( 'mouseleave', () => {
56
+ timeOut = setTimeout( () => {
57
+ if ( hideOnMouseOut ) {
58
+ if ( 'block' === popoverEl.style.display ) {
59
+ hidePopover();
60
+ }
61
+ }
62
+ }, hideAfter );
63
+ } );
64
+
65
+ // Don't hide the popover if the user is still hovering over it.
66
+ popoverEl.addEventListener( 'mouseover', () => {
67
+ hideOnMouseOut = false;
68
+
69
+ if ( timeOut ) {
70
+ clearTimeout( timeOut );
71
+
72
+ timeOut = null;
73
+ }
74
+ } );
75
+
76
+ // Once the user stops hovering over the popover, hide it.
77
+ popoverEl.addEventListener( 'mouseleave', () => {
78
+ timeOut = setTimeout( () => {
79
+ if ( hideOnMouseOut ) {
80
+ if ( 'block' === popoverEl.style.display ) {
81
+ hidePopover();
82
+ }
83
+ }
84
+ }, hideAfter );
85
+
86
+ hideOnMouseOut = true;
87
+ } );
88
+ };
89
+
90
+ /**
91
+ * Handle the Popover's click functionality
92
+ */
93
+ const handlePopoverClick = () => {
94
+ let popoverIsActive = false;
95
+
96
+ target.addEventListener( 'click', ( e ) => {
97
+ e.preventDefault();
98
+ e.stopPropagation();
99
+
100
+ if ( popoverIsActive ) {
101
+ hidePopover();
102
+
103
+ popoverIsActive = false;
104
+ } else {
105
+ showPopover();
106
+
107
+ popoverIsActive = true;
108
+ }
109
+ } );
110
+
111
+ // Make sure the popover doesn't close when it is clicked on.
112
+ popoverEl.addEventListener( 'click', ( e ) => {
113
+ e.stopPropagation();
114
+ } );
115
+
116
+ // Hide the popover when clicking outside of it.
117
+ document.body.addEventListener( 'click', () => {
118
+ if ( popoverIsActive ) {
119
+ hidePopover();
120
+
121
+ popoverIsActive = false;
122
+ }
123
+ } );
124
+ };
125
+
126
+ if ( 'hover' === trigger ) {
127
+ handlePopoverHover();
128
+ } else if ( 'click' === trigger ) {
129
+ handlePopoverClick();
130
+ }
131
+ }, [ targetRef ] );
132
+
133
+ let wrapperClasses = 'e-app__popover';
134
+
135
+ if ( wrapperClass ) {
136
+ wrapperClasses += ' ' + wrapperClass;
137
+ }
138
+
139
+ return (
140
+ <div className={ wrapperClasses } ref={ popoverRef }>
141
+ { props.children }
142
+ </div>
143
+ );
144
+ }
145
+
146
+ PopoverDialog.propTypes = {
147
+ targetRef: PropTypes.oneOfType( [ PropTypes.func, PropTypes.shape( { current: PropTypes.any } ) ] ).isRequired,
148
+ trigger: PropTypes.string,
149
+ direction: PropTypes.string,
150
+ offsetTop: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number ] ),
151
+ offsetLeft: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number ] ),
152
+ wrapperClass: PropTypes.string,
153
+ children: PropTypes.any,
154
+ hideAfter: PropTypes.number,
155
+ };
156
+
157
+ PopoverDialog.defaultProps = {
158
+ direction: 'bottom',
159
+ trigger: 'hover',
160
+ offsetTop: 10,
161
+ offsetLeft: 0,
162
+ hideAfter: 300,
163
+ };
app/assets/js/ui/popover-dialog/popover-dialog.scss ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-app__popover {
2
+ display: none;
3
+ position: absolute;
4
+ box-shadow: 0 2px 15px rgba(0, 0, 0, 0.3);
5
+ border-radius: 6px;
6
+ padding: 20px;
7
+ width: fit-content;
8
+ z-index: 999;
9
+ background-color: $white;
10
+
11
+ $triangle-size: 8px;
12
+
13
+ &:before {
14
+ content: '';
15
+ position: absolute;
16
+ top: -$triangle-size * 2;
17
+ @include end(var(--popover-arrow-offset-end, 22px));
18
+ border: $triangle-size solid transparent;
19
+ border-bottom-color: #fff;
20
+ }
21
+ }
app/assets/js/ui/table/table-body.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ export default function TableBody( props ) {
4
+ return (
5
+ <tbody className={ arrayToClassName( [ 'eps-table__body', props.className ] ) }>
6
+ { props.children }
7
+ </tbody>
8
+ );
9
+ }
10
+
11
+ TableBody.propTypes = {
12
+ children: PropTypes.any.isRequired,
13
+ className: PropTypes.string,
14
+ };
app/assets/js/ui/table/table-cell.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ export default function TableCell( props ) {
4
+ const Element = () => React.createElement( props.tag, {
5
+ className: arrayToClassName( [ 'eps-table__cell', props.className ] ),
6
+ colSpan: props.colSpan || null,
7
+ }, props.children );
8
+
9
+ return <Element />;
10
+ }
11
+
12
+ TableCell.propTypes = {
13
+ children: PropTypes.any,
14
+ className: PropTypes.string,
15
+ colSpan: PropTypes.oneOfType( [ PropTypes.number, PropTypes.string ] ),
16
+ tag: PropTypes.oneOf( [ 'td', 'th' ] ).isRequired,
17
+ };
app/assets/js/ui/table/table-checkbox.js ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+
3
+ import { Context } from './table-context';
4
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
5
+
6
+ import Checkbox from 'elementor-app/ui/atoms/checkbox';
7
+
8
+ export default function TableCheckbox( props ) {
9
+ const context = useContext( Context ),
10
+ { selected, disabled, setSelected } = context || {},
11
+ isSelectAllCheckbox = Object.prototype.hasOwnProperty.call( props, 'allSelectedCount' ),
12
+ isAllSelected = selected.length === props.allSelectedCount,
13
+ isIndeterminate = isSelectAllCheckbox ? ! ! ( ( selected.length - disabled.length ) && ! isAllSelected ) : false,
14
+ isSelected = isSelectAllCheckbox ? isAllSelected : selected.includes( props.index ),
15
+ isDisabled = ! isSelectAllCheckbox ? disabled.includes( props.index ) : null,
16
+ onSelectAll = () => {
17
+ setSelected( () => {
18
+ if ( isAllSelected || isIndeterminate ) {
19
+ // Disabled checkboxes should not be unchecked.
20
+ return disabled.length ? [ ...disabled ] : [];
21
+ }
22
+
23
+ return Array( props.allSelectedCount )
24
+ .fill( true )
25
+ .map( ( value, index ) => index );
26
+ } );
27
+ },
28
+ onSelectRow = () => {
29
+ setSelected( ( prevState ) => {
30
+ const currentSelections = [ ...prevState ],
31
+ currentIndexPosition = currentSelections.indexOf( props.index );
32
+
33
+ if ( currentIndexPosition > -1 ) {
34
+ currentSelections.splice( currentIndexPosition, 1 );
35
+ } else {
36
+ currentSelections.push( props.index );
37
+ }
38
+
39
+ return currentSelections;
40
+ } );
41
+ },
42
+ onChange = () => isSelectAllCheckbox ? onSelectAll() : onSelectRow();
43
+
44
+ return (
45
+ <Checkbox
46
+ checked={ isSelected }
47
+ indeterminate={ isIndeterminate }
48
+ onChange={ onChange }
49
+ disabled={ isDisabled }
50
+ className={ arrayToClassName( [ 'eps-table__checkbox', props.className ] ) }
51
+ />
52
+ );
53
+ }
54
+
55
+ TableCheckbox.propTypes = {
56
+ className: PropTypes.string,
57
+ index: PropTypes.number,
58
+ initialChecked: PropTypes.bool,
59
+ allSelectedCount: PropTypes.number,
60
+ };
app/assets/js/ui/table/table-context.js ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ import React from 'react';
2
+
3
+ export const Context = React.createContext();
app/assets/js/ui/table/table-row.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ export default function TableRow( props ) {
4
+ return (
5
+ <tr className={ arrayToClassName( [ 'eps-table__row', props.className ] ) }>
6
+ { props.children }
7
+ </tr>
8
+ );
9
+ }
10
+
11
+ TableRow.propTypes = {
12
+ children: PropTypes.any.isRequired,
13
+ className: PropTypes.string,
14
+ };
app/assets/js/ui/table/table.head.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ export default function TableHead( props ) {
4
+ return (
5
+ <thead className={ arrayToClassName( [ 'eps-table__head', props.className ] ) }>
6
+ { props.children }
7
+ </thead>
8
+ );
9
+ }
10
+
11
+ TableHead.propTypes = {
12
+ children: PropTypes.any.isRequired,
13
+ className: PropTypes.string,
14
+ };
app/assets/js/ui/table/table.js ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react';
2
+
3
+ import { Context } from './table-context';
4
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
5
+
6
+ import TableHead from './table.head';
7
+ import TableBody from './table-body';
8
+ import TableRow from './table-row';
9
+ import TableCell from './table-cell';
10
+ import TableCheckbox from './table-checkbox';
11
+
12
+ import './table.scss';
13
+
14
+ export default function Table( { className, initialSelected, initialDisabled, selection, children, onSelect } ) {
15
+ const [ selected, setSelected ] = useState( initialSelected ),
16
+ [ disabled, setDisabled ] = useState( initialDisabled ),
17
+ classNameBase = 'eps-table',
18
+ classes = [ classNameBase, { [ classNameBase + '--selection' ]: selection }, className ];
19
+
20
+ useEffect( () => {
21
+ if ( onSelect ) {
22
+ onSelect( selected );
23
+ }
24
+ }, [ selected ] );
25
+
26
+ return (
27
+ <Context.Provider value={ { selected, setSelected, disabled, setDisabled } }>
28
+ <table className={ arrayToClassName( classes ) }>
29
+ {
30
+ selection &&
31
+ <colgroup>
32
+ <col className={ classNameBase + '__checkboxes-column' } />
33
+ </colgroup>
34
+ }
35
+
36
+ { children }
37
+ </table>
38
+ </Context.Provider>
39
+ );
40
+ }
41
+
42
+ Table.Head = TableHead;
43
+ Table.Body = TableBody;
44
+ Table.Row = TableRow;
45
+ Table.Cell = TableCell;
46
+ Table.Checkbox = TableCheckbox;
47
+
48
+ Table.propTypes = {
49
+ children: PropTypes.any.isRequired,
50
+ className: PropTypes.string,
51
+ headers: PropTypes.array,
52
+ initialDisabled: PropTypes.array,
53
+ initialSelected: PropTypes.array,
54
+ rows: PropTypes.array,
55
+ selection: PropTypes.bool,
56
+ onSelect: PropTypes.func,
57
+ };
58
+
59
+ Table.defaultProps = {
60
+ selection: false,
61
+ initialDisabled: [],
62
+ initialSelected: [],
63
+ };
app/assets/js/ui/table/table.scss ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-table-body-color: tints(700);
2
+ $eps-table-body-dark-color: dark-tints(200);
3
+ $eps-table-body-background-color: theme-colors(light);
4
+ $eps-table-body-dark-background-color: dark-tints(600);
5
+
6
+ $eps-table-body-cell-start-radius: #{$eps-radius} 0 0 #{$eps-radius};
7
+ $eps-table-body-cell-end-radius: 0 #{$eps-radius} #{$eps-radius} 0;
8
+
9
+ $root: eps-table;
10
+
11
+ .#{$root} {
12
+ $parent: &;
13
+
14
+ --eps-table-body-color: #{$eps-table-body-color};
15
+ --eps-table-body-background-color: #{$eps-table-body-background-color};
16
+
17
+ border-spacing: 0 2px;
18
+ table-layout: fixed;
19
+ width: 100%;
20
+
21
+ &__checkboxes-column {
22
+ width: spacing(30);
23
+ }
24
+
25
+ &__checkbox {
26
+ display: flex;
27
+ flex-shrink: 0;
28
+ }
29
+
30
+ &__cell {
31
+ padding: spacing(16);
32
+ }
33
+
34
+ &__head {
35
+ #{$parent}__cell {
36
+ text-align: start;
37
+ }
38
+ }
39
+
40
+ &__body {
41
+ #{$parent}__row {
42
+ background-color: var(--eps-table-body-background-color);
43
+ }
44
+
45
+ #{$parent}__cell:first-child {
46
+ border-radius: $eps-table-body-cell-start-radius;
47
+ }
48
+
49
+ #{$parent}__cell:last-child {
50
+ border-radius: $eps-table-body-cell-end-radius;
51
+ }
52
+ }
53
+
54
+ &--selection {
55
+ #{$parent}__cell:first-child {
56
+ padding-inline-end: 0;
57
+ }
58
+ }
59
+ }
60
+
61
+ .eps-theme-dark {
62
+ .#{$root} {
63
+ --eps-table-body-color: #{$eps-table-body-dark-color};
64
+ --eps-table-body-background-color: #{$eps-table-body-dark-background-color};
65
+ }
66
+ }
67
+
68
+ [dir="rtl"] {
69
+ .#{$root} {
70
+ $parent: &;
71
+
72
+ &__body {
73
+ #{$parent}__cell:first-child {
74
+ border-radius: $eps-table-body-cell-end-radius;
75
+ }
76
+
77
+ #{$parent}__cell:last-child {
78
+ border-radius: $eps-table-body-cell-start-radius;
79
+ }
80
+ }
81
+ }
82
+ }
app/assets/js/url-actions/actions-map.js ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ export default {
2
+ 'import-kit': '/import/process',
3
+ };
app/assets/js/utils/utils.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const pxToRem = ( pixels ) => {
2
+ if ( ! pixels ) {
3
+ return;
4
+ } else if ( 'string' !== typeof pixels ) {
5
+ pixels = pixels.toString();
6
+ }
7
+
8
+ return pixels
9
+ .split( ' ' )
10
+ .map( ( value ) => `${ value * 0.0625 }rem` )
11
+ .join( ' ' );
12
+ };
13
+
14
+ export const arrayToClassName = ( array, action ) => {
15
+ return array
16
+ .filter( ( item ) => 'object' === typeof ( item ) ? Object.entries( item )[ 0 ][ 1 ] : item )
17
+ .map( ( item ) => {
18
+ const value = 'object' === typeof ( item ) ? Object.entries( item )[ 0 ][ 0 ] : item;
19
+
20
+ return action ? action( value ) : value;
21
+ } )
22
+ .join( ' ' );
23
+ };
24
+
25
+ export const stringToRemValues = ( string ) => {
26
+ return string
27
+ .split( ' ' )
28
+ .map( ( value ) => pxToRem( value ) )
29
+ .join( ' ' );
30
+ };
31
+
32
+ export const rgbToHex = ( r, g, b ) => '#' + [ r, g, b ].map( ( x ) => {
33
+ const hex = x.toString( 16 );
34
+ return 1 === hex.length ? '0' + hex : hex;
35
+ } ).join( '' );
36
+
37
+ export const isOneOf = ( filetype, filetypeOptions ) => {
38
+ return filetypeOptions.some( ( type ) => filetype.includes( type ) );
39
+ };
40
+
41
+ export const arrayToObjectByKey = ( array, key ) => {
42
+ const finalObject = {};
43
+
44
+ array.forEach( ( item ) => finalObject[ item[ key ] ] = item );
45
+
46
+ return finalObject;
47
+ };
app/assets/styles/_base.scss ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ @import "//fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap";
2
+ @import "base/custom-properties";
3
+ @import "base/dark/dark-custom-properties";
4
+ @import "base/reset";
5
+ @import "base/typography";
6
+ @import "shame";
app/assets/styles/_common.scss ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ @import "./functions";
2
+ @import "./tokens";
3
+ @import "./mixins";
app/assets/styles/_functions.scss ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ // Functions
2
+ @import "functions/deep-map";
3
+ @import "functions/map-collect";
4
+ @import "functions/prefix";
5
+ @import "functions/px-to-rem";
app/assets/styles/_helpers.scss ADDED
File without changes
app/assets/styles/_mixins.scss ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ // MIXINS: Common mixins
2
+ @import "mixins/create-atom";
3
+ @import "mixins/screen-reader";
4
+ @import "mixins/text-truncate";
app/assets/styles/_shame.scss ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SHAME:
2
+ // Purpose of this partial is to gather all ad hock, not structured code.
3
+ // This will enable to refactor it bit by bit into structured code without effecting current output.
4
+
5
+ //todo: discuss, is this needed?
6
+
7
+ .video-wrapper {
8
+ position: relative;
9
+ padding-bottom: $eps-ratio-16-9;
10
+ height: 0;
11
+
12
+ iframe {
13
+ position: absolute;
14
+ top: 0;
15
+ left: 0;
16
+ width: 100%;
17
+ height: 100%;
18
+ }
19
+ }
20
+
21
+ .#{$eps-prefix}separator {
22
+ margin-bottom: spacing(44);
23
+ }
24
+
25
+
26
+ .eps-theme-dark {
27
+ --e-app-back-button-color: #{dark-tints(200)};
28
+ }
29
+
30
+ .back-button, .e-app-back-button {
31
+ --button-background-color: var(--e-app-back-button-color, #{tints(500)});
32
+ margin-bottom: spacing(24);
33
+
34
+ .eps-icon {
35
+ margin-inline-end: spacing(5);
36
+ }
37
+ }
38
+
39
+ .eps-theme-dark {
40
+ --input-border-color: --hr-color;
41
+ }
42
+
43
+ .#{$eps-prefix}input {
44
+ border: 1px solid var(--hr-color);
45
+ border-radius: $eps-radius;
46
+ background: transparent;
47
+ color: inherit;
48
+ height: spacing(30);
49
+ padding: 0 spacing(5);
50
+
51
+ &--block {
52
+ width: 100%;
53
+ }
54
+ }
app/assets/styles/_tokens.scss ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Global tokens
2
+ // - Sort Order has meaning
3
+ // -- Core
4
+ @import "./tokens/global";
5
+ @import "./tokens/colors";
6
+ @import "./tokens/theme";
7
+ @import "./tokens/spacing";
8
+ @import "./tokens/font";
9
+ @import "./tokens/type";
10
+ @import "./tokens/ratio";
11
+ @import "./tokens/opacity";
12
+ @import "./tokens/z-index";
13
+
14
+ // - Sort Order is A-Z
15
+ // -- These tokens might use definitions from core layer
16
+ @import "./tokens/styles/border";
17
+ @import "./tokens/styles/motion";
18
+ @import "./tokens/styles/radius";
19
+ @import "./tokens/styles/shadow";
20
+
21
+ // Maps
22
+ // - Sorted by roles
23
+ @import "./tokens/maps/opacity-map";
24
+ @import "./tokens/maps/theme-map";
25
+ @import "./tokens/maps/tints-map";
26
+ @import "./tokens/maps/spacing-map";
27
+ @import "./tokens/maps/type-map";
28
+ @import "./tokens/maps/z-index-map";
29
+ @import "./tokens/maps/shadow-map";
30
+ @import "./tokens/maps/atoms";
31
+
32
+ // Map Semantic functions
33
+ @import "./tokens/map-functions";
34
+
35
+ // Themes
36
+ @import "themes/dark-tokens";
37
+
app/assets/styles/app-imports.scss ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "../js/app.scss";
2
+ @import "../js/molecules/upload-file.scss";
3
+ @import "../js/organisms/drop-zone.scss";
4
+ @import "../../modules/site-editor/assets/js/molecules/site-part.scss";
5
+ @import "../../modules/site-editor/assets/js/templates/site-editor.scss";
6
+ @import "../../modules/site-editor/assets/scss/loading.scss";
7
+ @import "../../modules/site-editor/assets/js/organisms/menu.scss";
8
+ @import "../../modules/import-export/assets/js/pages/import/import-kit/import-kit.scss";
9
+ @import "../../modules/site-editor/assets/js/pages/promotion.scss";
10
+ @import "../../modules/import-export/assets/js/ui/wizard-step/wizard-step.scss";
11
+ @import "../../modules/import-export/assets/js/ui/page-header/page-header.scss";
12
+ @import "../../modules/import-export/assets/js/shared/kit-content/kit-content";
13
+ @import "../js/organisms/wizard-footer.scss";
14
+ @import "../../modules/import-export/assets/js/shared/kit-content/components/templates-features/templates-features.scss";
15
+ @import "../js/ui/card/card.scss";
16
+ @import "../js/ui/menu/menu-item.scss";
17
+ @import "../js/ui/grid/grid.scss";
18
+ @import "../js/ui/menu/menu.scss";
19
+ @import "../js/ui/modal/modal.scss";
20
+ @import "../js/ui/molecules/add-new-button.scss";
21
+ @import "../js/ui/molecules/select2.scss";
22
+ @import "../js/ui/molecules/notice.scss";
23
+ @import "../js/ui/molecules/list.scss";
24
+ @import "../js/ui/molecules/popover.scss";
25
+ @import "../js/ui/atoms/css-grid.scss";
26
+ @import "../js/ui/atoms/box.scss";
27
+ @import "../js/ui/atoms/checkbox.scss";
28
+ @import "../js/ui/atoms/drag-drop.scss";
29
+ // Should be after buttons.scss to prevent dialog button style overwrite.
30
+ @import "../js/ui/dialog/dialog.scss";
31
+ @import "../js/ui/popover-dialog/popover-dialog.scss";
32
+ @import "../js/ui/molecules/inline-link.scss";
33
+ @import "../js/ui/atoms/text-field.scss";
34
+ @import "../../modules/import-export/assets/js/shared/content-layout/content-layout.scss";
35
+ @import "../../modules/import-export/assets/js/pages/export/export-complete/export-complete.scss";
36
+ @import "../../modules/import-export/assets/js/shared/kit-content/kit-content.scss";
37
+ @import "../../modules/import-export/assets/js/shared/kit-data/kit-data.scss";
38
+ @import "../../modules/import-export/assets/js/pages/import/import-resolver/import-resolver.scss";
39
+ @import "../js/ui/panel/panel.scss";
40
+ @import "../../modules/import-export/assets/js/pages/export/export-kit/export-kit.scss";
41
+ @import "../../modules/import-export/assets/js/shared/info-modal/info-modal.scss";
42
+
43
+ // Kit library
44
+ @import "../../modules/kit-library/assets/js/components/badge.scss";
45
+ @import "../../modules/kit-library/assets/js/components/collapse.scss";
46
+ @import "../../modules/kit-library/assets/js/components/envato-promotion.scss";
47
+ @import "../../modules/kit-library/assets/js/components/error-screen.scss";
48
+ @import "../../modules/kit-library/assets/js/components/favorites-actions.scss";
49
+ @import "../../modules/kit-library/assets/js/components/filter-indication-text.scss";
50
+ @import "../../modules/kit-library/assets/js/components/item-header.scss";
51
+ @import "../../modules/kit-library/assets/js/components/kit-list-item.scss";
52
+ @import "../../modules/kit-library/assets/js/components/layout/header-back-button.scss";
53
+ @import "../../modules/kit-library/assets/js/components/page-loader.scss";
54
+ @import "../../modules/kit-library/assets/js/components/search-input.scss";
55
+ @import "../../modules/kit-library/assets/js/components/sort-select.scss";
56
+ @import "../../modules/kit-library/assets/js/components/tags-filter.scss";
57
+ @import "../../modules/kit-library/assets/js/pages/index/index-header.scss";
58
+ @import "../../modules/kit-library/assets/js/pages/index/index.scss";
59
+ @import "../../modules/kit-library/assets/js/pages/overview/overview-sidebar.scss";
60
+ @import "../../modules/kit-library/assets/js/pages/overview/overview.scss";
61
+ @import "../../modules/kit-library/assets/js/pages/preview/preview-responsive-controls.scss";
62
+ @import "../../modules/kit-library/assets/js/pages/preview/preview.scss";
63
+ @import "../js/molecules/collapse.scss";
64
+ @import "../../modules/import-export/assets/js/pages/import/import-plugins/import-plugins.scss";
65
+ @import "../js/ui/table/table.scss";
66
+ @import "../../modules/import-export/assets/js/pages/import/import-plugins/components/pro-banner/pro-banner.scss";
67
+ @import "../../modules/import-export/assets/js/pages/export/export-plugins/export-plugins.scss";
68
+ @import "../../modules/import-export/assets/js/pages/import/import-content/import-content.scss";
69
+ @import "../../modules/import-export/assets/js/pages/import/import-plugins-activation/import-plugins-activation.scss";
70
+ @import "../../modules/import-export/assets/js/shared/plugins-selection/components/plugins-table.scss";
71
+ @import "../../modules/import-export/assets/js/ui/loader/loader.scss";
72
+ @import "../../modules/import-export/assets/js/ui/message-banner/message-banner.scss";
73
+ @import "../../modules/import-export/assets/js/pages/import/import-complete/components/connect-pro-notice/connect-pro-notice.scss";
74
+ @import "../../modules/import-export/assets/js/pages/import/import-complete/components/failed-plugins-notice/failed-plugins-notice.scss";
75
+
76
+ // Onboarding
77
+ @import "../../modules/onboarding/assets/scss/onboarding";
78
+ @import "../../modules/onboarding/assets/js/components/layout/layout";
79
+ @import "../../modules/onboarding/assets/js/components/layout/header";
80
+ @import "../../modules/onboarding/assets/js/components/progress-bar/progress-bar";
81
+ @import "../../modules/onboarding/assets/js/components/button";
82
+ @import "../../modules/onboarding/assets/js/components/card";
83
+ @import "../../modules/onboarding/assets/js/components/checklist";
84
+ @import "../../modules/onboarding/assets/js/components/notice";
85
+ @import "../../modules/onboarding/assets/js/pages/account";
86
+ @import "../../modules/onboarding/assets/js/pages/hello-theme";
87
+ @import "../../modules/onboarding/assets/js/pages/site-name";
88
+ @import "../../modules/onboarding/assets/js/pages/site-logo";
89
+ @import "../../modules/onboarding/assets/js/pages/good-to-go";
90
+ @import "../../modules/onboarding/assets/js/pages/upload-and-install-pro";
app/assets/styles/base/_animations.scss ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-pop-animation: eps-animation-pop 0.15s cubic-bezier(0.57, 0.53, 0.71, 1.47) forwards;
2
+
3
+ @keyframes eps-animation-pop {
4
+ from {
5
+ transform: scale(0.75);
6
+ opacity: 0;
7
+ }
8
+ to {
9
+ transform: scale(1);
10
+ opacity: 1;
11
+ }
12
+ }
app/assets/styles/base/_custom-properties.scss ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Base: Custom properties
2
+ // - Generated via loops from relevant maps.
3
+
4
+ // Custom variable values only support SassScript inside `#{}`.
5
+
6
+ :root {
7
+ @each $color, $value in $eps-theme-colors {
8
+ @if map-get($eps-theme-colors, $color) {
9
+ --#{$color}: #{$value};
10
+ }
11
+ }
12
+
13
+ @each $color, $value in $eps-theme-elements-colors {
14
+ @if map-get($eps-theme-elements-colors, $color) {
15
+ --#{$color}: #{$value};
16
+ }
17
+ }
18
+
19
+ @each $color, $value in $eps-tints {
20
+ @if map-get($eps-tints, $color) {
21
+ --gray-#{$color}: #{$value};
22
+ }
23
+ }
24
+ }
app/assets/styles/base/_layout.scss ADDED
File without changes
app/assets/styles/base/_reset.scss ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // BASE: Reset
2
+ *,
3
+ *::before,
4
+ *::after {
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ html {
9
+ font-family: sans-serif;
10
+ line-height: 1.15;
11
+ -webkit-text-size-adjust: 100%;
12
+ -webkit-tap-highlight-color: rgba($black, 0);
13
+ }
14
+
15
+ // stylelint-disable-next-line selector-list-comma-newline-after
16
+ article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
17
+ display: block;
18
+ }
19
+
20
+ // Body
21
+
22
+ body {
23
+ margin: 0;
24
+ font-family: $eps-font-family;
25
+ font-size: $eps-body-font-size;
26
+ font-weight: $eps-font-weight-base;
27
+ line-height: $eps-line-height-base;
28
+ color: var(--body-color);
29
+ -webkit-font-smoothing: antialiased;
30
+ -moz-osx-font-smoothing: grayscale;
31
+ background-color: var(--body-bg);
32
+ }
33
+
34
+ ::selection {
35
+ background-color: rgba(theme-colors(cta), $opacity-05);
36
+ }
37
+
38
+ [tabindex="-1"]:focus:not(:focus-visible) {
39
+ outline: 0 !important;
40
+ }
41
+
42
+ // Typography
43
+ // Reset header Tags
44
+ h1, h2, h3, h4, h5, h6 {
45
+ font-size:100%;
46
+ margin:0;
47
+ padding:0;
48
+ line-height:inherit;
49
+ font-weight:normal;
50
+ }
51
+
52
+ // Reset margins on paragraphs
53
+ p {
54
+ margin-top: 0;
55
+ margin-bottom: $eps-paragraph-margin-bottom;
56
+ }
57
+
58
+ b,
59
+ strong {
60
+ font-weight: $eps-font-weight-bold; // Add the correct font weight in Chrome, Edge, and Safari
61
+ }
62
+
63
+ small {
64
+ font-size: $eps-small-font-size; // Add the correct font size in all browsers
65
+ }
66
+
67
+ // Links
68
+ a {
69
+ --eps-link-color: $eps-link-color;
70
+ color: var(--eps-link-color);
71
+ background-color: transparent; // Remove the gray background on active links in IE 10.
72
+
73
+ &,
74
+ &:active,
75
+ &:hover,
76
+ &:focus {
77
+ text-decoration: $eps-link-decoration;
78
+ }
79
+
80
+ &:focus,
81
+ &:hover {
82
+ --eps-link-color: $eps-link-hover-color;
83
+ text-decoration: $eps-link-hover-decoration;
84
+ }
85
+ }
86
+
87
+ a:not([href]) {
88
+ color: inherit;
89
+ text-decoration: none;
90
+
91
+ &:hover {
92
+ color: inherit;
93
+ text-decoration: none;
94
+ }
95
+ }
96
+
97
+ // Code
98
+ pre,
99
+ code,
100
+ kbd,
101
+ samp {
102
+ font-family: $font-family-monospace;
103
+ font-size: 1em;
104
+ }
105
+
106
+ // Figures
107
+ figure {
108
+ // Apply a consistent margin strategy (matches our type styles).
109
+ margin: 0 0 0;
110
+ }
111
+
112
+ // Images and content
113
+
114
+ img {
115
+ vertical-align: middle;
116
+ border-style: none; // Remove the border on images inside links in IE 10-.
117
+ }
118
+
119
+ svg {
120
+ overflow: hidden;
121
+ vertical-align: middle;
122
+ }
123
+
124
+
125
+ button {
126
+ border-radius: 0;
127
+ }
128
+
129
+ button:focus {
130
+ outline: 1px dotted;
131
+ outline: 5px auto -webkit-focus-ring-color;
132
+ }
133
+
134
+ input,
135
+ button,
136
+ select,
137
+ optgroup,
138
+ textarea {
139
+ margin: 0;
140
+ font-family: inherit;
141
+ font-size: inherit;
142
+ line-height: inherit;
143
+ }
144
+
145
+ button,
146
+ input {
147
+ overflow: visible; // Show the overflow in Edge
148
+ }
149
+
150
+ button,
151
+ select {
152
+ text-transform: none; // Remove the inheritance of text transform in Firefox
153
+ }
154
+
155
+ [role="button"] {
156
+ cursor: pointer;
157
+ }
158
+
159
+ select {
160
+ word-wrap: normal;
161
+ }
162
+
163
+
164
+ button,
165
+ [type="button"],
166
+ [type="reset"],
167
+ [type="submit"] {
168
+ -webkit-appearance: button;
169
+ }
170
+
171
+ // Opinionated: add "hand" cursor to non-disabled button elements.
172
+ button,
173
+ [type="button"],
174
+ [type="reset"],
175
+ [type="submit"] {
176
+ &:not(:disabled) {
177
+ cursor: pointer;
178
+ }
179
+ }
180
+
181
+ // Remove inner border and padding from Firefox, but don't restore the outline like Normalize.
182
+ button::-moz-focus-inner,
183
+ [type="button"]::-moz-focus-inner,
184
+ [type="reset"]::-moz-focus-inner,
185
+ [type="submit"]::-moz-focus-inner {
186
+ padding: 0;
187
+ border-style: none;
188
+ }
189
+
190
+ input[type="radio"],
191
+ input[type="checkbox"] {
192
+ box-sizing: border-box;
193
+ padding: 0;
194
+ }
195
+
196
+
197
+ textarea {
198
+ overflow: auto;
199
+ resize: vertical;
200
+ }
201
+
202
+
203
+ // Always hide an element with the `hidden` HTML attribute (from PureCSS).
204
+ [hidden] {
205
+ display: none !important;
206
+ }
207
+
208
+ // Opinionated resets:
209
+
210
+ hr {
211
+ border: 0 none;
212
+ border-bottom: 1px solid var(--hr-color);
213
+ }
app/assets/styles/base/_transitions.scss ADDED
File without changes
app/assets/styles/base/_typography.scss ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // TYPOGRAPHY: Atoms, tags, reusable typography elements
2
+ .#{$eps-prefix}display-1 {
3
+ @include create-atom(typography, display, 1);
4
+ }
5
+
6
+ .#{$eps-prefix}display-2 {
7
+ @include create-atom(typography, display, 2);
8
+ }
9
+
10
+ .#{$eps-prefix}display-3 {
11
+ @include create-atom(typography, display, 3);
12
+ }
13
+
14
+ .#{$eps-prefix}display-4 {
15
+ @include create-atom(typography, display, 4);
16
+ }
17
+
18
+
19
+ h1,
20
+ .#{$eps-prefix}h1 {
21
+ @include create-atom(typography, heading, 1);
22
+ }
23
+
24
+ h2,
25
+ .#{$eps-prefix}h2 {
26
+ @include create-atom(typography, heading, 2);
27
+ }
28
+
29
+ h3,
30
+ .#{$eps-prefix}h3 {
31
+ @include create-atom(typography, heading, 3);
32
+ }
33
+
34
+ h4,
35
+ .#{$eps-prefix}h4 {
36
+ @include create-atom(typography, heading, 4);
37
+ }
38
+
39
+ h5,
40
+ .#{$eps-prefix}h5 {
41
+ @include create-atom(typography, heading, 5);
42
+ }
43
+
44
+ h6,
45
+ .#{$eps-prefix}h6 {
46
+ @include create-atom(typography, heading, 6);
47
+ }
48
+
49
+ // semantic sizing for text
50
+ // todo: should be put !importatnt here? update code and remove comment
51
+ .#{$eps-prefix}text-xxs {
52
+ @include create-atom(typography, text, xxs);
53
+ }
54
+
55
+ .#{$eps-prefix}text-xs {
56
+ @include create-atom(typography, text, xs);
57
+ }
58
+
59
+ .#{$eps-prefix}text {
60
+ @include create-atom(typography, text, base);
61
+ }
62
+
63
+ .#{$eps-prefix}text-sm {
64
+ @include create-atom(typography, text, sm);
65
+ }
66
+
67
+ .#{$eps-prefix}text-lg {
68
+ @include create-atom(typography, text, lg);
69
+ }
70
+
71
+ .#{$eps-prefix}text-xl {
72
+ @include create-atom(typography, text, xl);
73
+ }
74
+
75
+ // todo: placeholders for future semantic work, update or remove
76
+ //.page-title{}
77
+ //.section-title{}
78
+ //.component-title{}
79
+ //.page-subtitle{}
80
+ //.section-subtitle{}
81
+ //.component-subtitle{}
app/assets/styles/base/_utilities.scss ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .sr-only {
2
+ @include sr-only();
3
+ }
4
+
5
+ @each $breakpoint in map-keys($breakpoints) {
6
+ @media screen and (min-width: #{map-get($breakpoints, $breakpoint)}) {
7
+ .text-start-#{$breakpoint} {
8
+ text-align: start;
9
+ }
10
+ }
11
+
12
+ @media screen and (min-width: #{map-get($breakpoints, $breakpoint)}) {
13
+ .text-center-#{$breakpoint} {
14
+ text-align: center;
15
+ }
16
+ }
17
+
18
+ @media screen and (min-width: #{map-get($breakpoints, $breakpoint)}) {
19
+ .text-end-#{$breakpoint} {
20
+ text-align: end;
21
+ }
22
+ }
23
+ }
app/assets/styles/base/dark/_dark-custom-properties.scss ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Base: Custom properties
2
+ // - Generated via loops from relevant maps.
3
+
4
+ // Custom variable values only support SassScript inside `#{}`.
5
+ .eps-theme-dark {
6
+ @each $color, $value in $eps-dark-theme-colors {
7
+ @if map-get($eps-dark-theme-colors, $color) {
8
+ --#{$color}: #{$value};
9
+ }
10
+ }
11
+
12
+ @each $color, $value in $eps-dark-theme-elements-colors {
13
+ @if map-get($eps-dark-theme-elements-colors, $color) {
14
+ --#{$color}: #{$value};
15
+ }
16
+ }
17
+
18
+ @each $color, $value in $eps-dark-tints {
19
+ @if map-get($eps-dark-tints, $color) {
20
+ --gray-#{$color}: #{$value};
21
+ }
22
+ }
23
+ }
app/assets/styles/functions/_border.scss ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ // Function: Border
2
+ // - @argument: $border-colorm $border-size(default is 1px), $border-style(default is 'solid')
3
+ // - @returns 'border values according to @arguments
4
+ // - use cases:
5
+ // - defining a border without specifying default values
6
+ // - passing semantic values as @arguments
7
+
8
+ @function border($border-color, $border-size:1px, $border-style:solid){
9
+ @return #{$border-size} #{$border-style} #{$border-color};
10
+ }
app/assets/styles/functions/_deep-map.scss ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ // map deep get
2
+ @function map-deep-get($map, $keys...) {
3
+ @each $key in $keys {
4
+ @if map-has-key($map, $key) {
5
+ $map: map-get($map, $key);
6
+ } @else {
7
+ @error "arguments are:#{$keys}, no key #{$key} found, did you mean #{map-keys($map)}?";
8
+ }
9
+ }
10
+ @return $map;
11
+ }
app/assets/styles/functions/_map-collect.scss ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //Map Collect function
2
+ // Since the builtin map-merge function in Sass only take 2 arguments, it can only merge 2 maps at a time.
3
+ // The map-collect function below allows you to combine multiple maps together in a cleaner way.
4
+
5
+ @function map-collect($maps...) {
6
+ $collection: ();
7
+
8
+ @each $map in $maps {
9
+ $collection: map-merge($collection, $map);
10
+ }
11
+ @return $collection;
12
+ }
app/assets/styles/functions/_prefix.scss ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Function: Prefix
2
+ // - Argument: string
3
+ // - Output string + hypen, E.g: .#{$prefix}some-element => .prefix-some-element
4
+
5
+
6
+ @function prefix($prefix:null){
7
+ @if $prefix{
8
+ @return $prefix + "-";
9
+ } @else {
10
+ @return null;
11
+ }
12
+ }
app/assets/styles/functions/_px-to-rem.scss ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ @function px-to-rem($multiplier, $unit:0.0625rem) {
2
+ @return $multiplier * $unit;
3
+ }
app/assets/styles/functions/_size.scss ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ // Function: size
2
+ // Simple Rem based multiplier
3
+ // - Use to convert pixel dimension to REM units
4
+ // - Default is 0.5rem (8px) which is our Spacing unit.
5
+ // - Optional: Add variable based value to $unit when 1rem is not 16px.
6
+ // E.g: size(5) will output: 2.5rem which is 40px;
7
+
8
+ @function size($multiplier, $unit:0.5rem) {
9
+ @return $multiplier * $unit;
10
+ }
app/assets/styles/mixins/_create-atom.scss ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // MIXIN: Create Atom
2
+ // - This mixin is a small to for creating an atom selector
3
+ // - This mixin uses the 'atomize' function which uses '$eps-atoms' map
4
+ // - Each Atom is configured within map.
5
+ // - Map key are 'properties' and values are mapped to tokens.
6
+ // - Please see 'tokens/,aps/_atoms.css' for more info.
7
+ @mixin create-atom($keys...) {
8
+ $map: atomizer($keys...);
9
+ @each $key, $value in $map {
10
+ #{$key}: $value;
11
+ }
12
+ }
app/assets/styles/mixins/_screen-reader.scss ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Only display content to screen readers
2
+ //
3
+ // See: https://a11yproject.com/posts/how-to-hide-content/
4
+ // See: https://hugogiraudel.com/2016/10/13/css-hide-and-seek/
5
+
6
+ @mixin sr-only() {
7
+ position: absolute;
8
+ width: 1px;
9
+ height: 1px;
10
+ padding: 0;
11
+ margin: -1px; // Fix for https://github.com/twbs/bootstrap/issues/25686
12
+ overflow: hidden;
13
+ clip: rect(0, 0, 0, 0);
14
+ white-space: nowrap;
15
+ border: 0;
16
+ }
17
+
18
+ // Use in conjunction with .sr-only to only display content when it's focused.
19
+ //
20
+ // Useful for "Skip to main content" links; see https://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
21
+ //
22
+ // Credit: HTML5 Boilerplate
23
+
24
+ @mixin sr-only-focusable() {
25
+ &:active,
26
+ &:focus {
27
+ position: static;
28
+ width: auto;
29
+ height: auto;
30
+ overflow: visible;
31
+ clip: auto;
32
+ white-space: normal;
33
+ }
34
+ }
app/assets/styles/mixins/_text-truncate.scss ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ // Utilities: Text truncate
2
+ // Requires inline-block or block for proper styling
3
+
4
+ @mixin text-truncate() {
5
+ overflow: hidden;
6
+ text-overflow: ellipsis;
7
+ white-space: nowrap;
8
+ }
app/assets/styles/themes/_dark-tokens.scss ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Scheme: Dark,
2
+ // -- tokens & maps
3
+ // dark theme
4
+ @import "dark/tokens/dark-theme";
5
+
6
+ // dark styles tokens
7
+ @import "dark/tokens/styles/dark-shadow";
8
+
9
+ // Maps
10
+ @import "dark/tokens/maps/dark-theme-map";
11
+ @import "dark/tokens/maps/dark-tints-map";
12
+
13
+ // Functions
14
+ @import "dark/tokens/dark-map-functions";
app/assets/styles/themes/dark/tokens/_dark-map-functions.scss ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Theme: Dark | Tokens: map functions
2
+ // - Theme specific map based functions
3
+ // Below functions can be duplicated and modified for Themes
4
+ @function dark-theme-colors($keys...) {
5
+ @return map-deep-get($eps-dark-theme-colors, $keys...);
6
+ }
7
+
8
+ @function dark-theme-elements-colors($keys...) {
9
+ @return map-deep-get($eps-dark-theme-elements-colors, $keys...);
10
+ }
11
+
12
+ @function dark-tints($keys...) {
13
+ @return map-deep-get($eps-dark-tints, $keys...);
14
+ }
app/assets/styles/themes/dark/tokens/_dark-theme.scss ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Dark Theme
2
+ // - Gray Semantics
3
+ $eps-dark-gray-800: $color-gray-anthracite;
4
+ $eps-dark-gray-700: $color-gray-metalise;
5
+ $eps-dark-gray-600: $color-gray-napoleon;
6
+ $eps-dark-gray-500: $color-gray-abbey;
7
+ $eps-dark-gray-400: $color-gray-stone-hearth;
8
+ $eps-dark-gray-300: $color-gray-silver-filigree;
9
+ $eps-dark-gray-200: $color-gray-brainstem;
10
+ $eps-dark-gray-100: $color-gray-yin-bai-silver;
11
+
12
+ // Theme semantics
13
+ // - data context
14
+ $eps-dark-theme-text-muted: $eps-dark-gray-300;
15
+ $eps-dark-theme-disabled: $eps-dark-gray-400;
16
+ $eps-dark-theme-light: $color-white;
17
+ $eps-dark-theme-dark: $color-black;
18
+ $eps-dark-theme-info: $color-cyan-ionized-air-glow;
19
+ $eps-dark-theme-accent: $color-cyan-ionized-air-glow;
20
+ $eps-dark-theme-danger: $color-red-tomato-frog;
21
+ $eps-dark-theme-cta: $color-red-rose-garnet;
22
+ $eps-dark-theme-warning: $color-yellow-hot-sun;
23
+ $eps-dark-theme-success: $color-green-spandex;
24
+ // - use context
25
+ $eps-dark-theme-body-color: $eps-dark-gray-100;
26
+ $eps-dark-theme-body-bg: $eps-dark-theme-light;
27
+ $eps-dark-theme-link-color: $color-cyan-ionized-air-glow;
28
+ $eps-dark-theme-link-hover-color: darken($eps-link-color, 15%);
29
+ $eps-dark-theme-hr-color: $eps-dark-gray-500;
30
+ $eps-dark-theme-display-1-color: $eps-dark-gray-100;
31
+ $eps-dark-theme-display-2-color: $eps-dark-gray-100;
32
+ $eps-dark-theme-display-3-color: $eps-dark-gray-100;
33
+ $eps-dark-theme-display-4-color: $eps-dark-gray-100;
34
+ $eps-dark-theme-h1-color: $eps-dark-gray-100;
35
+ $eps-dark-theme-h2-color: $eps-dark-gray-100;
36
+ $eps-dark-theme-h3-color: $eps-dark-gray-100;
37
+ $eps-dark-theme-h4-color: $eps-dark-gray-100;
38
+ $eps-dark-theme-h5-color: $eps-dark-gray-100;
39
+ $eps-dark-theme-h6-color: $eps-dark-gray-100;
40
+ $eps-dark-theme-text-base-color: $eps-dark-gray-200;
41
+ $eps-dark-theme-text-xl-color: $eps-dark-gray-200;
42
+ $eps-dark-theme-text-lg-color: $eps-dark-gray-200;
43
+ $eps-dark-theme-text-sm-color: $eps-dark-gray-200;
44
+ $eps-dark-theme-text-xs-color: $eps-dark-gray-200;
45
+ $eps-dark-theme-text-xxs-color: $eps-dark-gray-200;
app/assets/styles/themes/dark/tokens/maps/_dark-theme-map.scss ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-dark-theme-colors: (
2
+ text-muted:$eps-dark-theme-text-muted,
3
+ disabled: $eps-dark-theme-disabled,
4
+ light: $eps-dark-theme-light,
5
+ dark: $eps-dark-theme-dark,
6
+ info:$eps-dark-theme-info,
7
+ accent:$eps-dark-theme-accent,
8
+ danger: $eps-dark-theme-danger,
9
+ cta: $eps-dark-theme-cta,
10
+ success: $eps-dark-theme-success,
11
+ warning: $eps-dark-theme-warning,
12
+ );
13
+
14
+ $eps-dark-theme-elements-colors: (
15
+ body-color: $eps-dark-theme-body-color,
16
+ body-bg:$eps-dark-theme-body-bg,
17
+ link-color: $eps-dark-theme-link-color,
18
+ link-hover-color: $eps-dark-theme-link-hover-color,
19
+ hr-color: $eps-dark-theme-hr-color,
20
+ box-shadow-color: $eps-dark-box-shadow-color,
21
+ display-1-color:$eps-dark-theme-display-1-color,
22
+ display-2-color:$eps-dark-theme-display-2-color,
23
+ display-3-color:$eps-dark-theme-display-3-color,
24
+ display-4-color:$eps-dark-theme-display-4-color,
25
+ h1-color:$eps-dark-theme-h1-color,
26
+ h2-color:$eps-dark-theme-h2-color,
27
+ h3-color:$eps-dark-theme-h3-color,
28
+ h4-color:$eps-dark-theme-h4-color,
29
+ h5-color:$eps-dark-theme-h5-color,
30
+ h6-color:$eps-dark-theme-h6-color,
31
+ text-base-color:$eps-dark-theme-text-base-color,
32
+ text-xl-color:$eps-dark-theme-text-xl-color,
33
+ text-lg-color:$eps-dark-theme-text-lg-color,
34
+ text-sm-color:$eps-dark-theme-text-sm-color,
35
+ text-xs-color:$eps-dark-theme-text-xs-color,
36
+ text-xxs-color:$eps-dark-theme-text-xxs-color,
37
+ )
app/assets/styles/themes/dark/tokens/maps/_dark-tints-map.scss ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ $eps-dark-tints: (
2
+ 800: $eps-dark-gray-800,
3
+ 700: $eps-dark-gray-700,
4
+ 600: $eps-dark-gray-600,
5
+ 500: $eps-dark-gray-500,
6
+ 400: $eps-dark-gray-400,
7
+ 300: $eps-dark-gray-300,
8
+ 200: $eps-dark-gray-200,
9
+ 100: $eps-dark-gray-100,
10
+ );
app/assets/styles/themes/dark/tokens/styles/_dark-shadow.scss ADDED
@@ -0,0 +1,2 @@
 
 
1
+ // TOKENS STYLES: DARK SHADOW presets
2
+ $eps-dark-box-shadow-color: rgba($eps-dark-theme-dark, $opacity-015);
app/assets/styles/tokens/_colors.scss ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // COLORS: colors registration, semantics and maps
2
+ // Flow:
3
+ // - register hex value as unique names (aliases)
4
+ // - comment color rgb,hsl values for further check
5
+ // - assign aliases to semantics
6
+ // - create color map with aliases as key, values
7
+ // - create semantic map with semantics as key, values
8
+ // -- eg: (malibu: $color-stack-malibu), (info:$brand-color-info)
9
+ // Tooling:
10
+ // https://www.color-hex.com/ get color specs
11
+ // https://colornamer.robertcooper.me/ generate unique names
12
+
13
+ // stacks: registered hex with unique names
14
+ $color-white: #fff; // RGB (255,255,255), HSL(0, 0%, 100%)
15
+ $color-black: #000; // RGB (0, 0, 0), HSL(0, 0%, 0%)
16
+ $color-cyan-ionized-air-glow: #58d0f5; // RGB(88,208,245), HSL(0.54, 0.89, 0.65)
17
+ $color-red-rose-garnet: #93003F; // RGB(211,12,92), HSL(0.93, 0.89, 0.44)
18
+ $color-red-carnelian: #b01b1b; // RGB(176,27,27), HSL(0, 0.73, 0.40)
19
+ $color-red-tomato-frog: #F84343; // RGB(248, 67, 67), HSL(0.00, 0.93, 0.62)
20
+ $color-yellow-hot-sun: #fcb92c; // RGB(252, 185, 44), HSL(0.11, 0.97, 0.58)
21
+ $color-green-spandex: #39b54a; // RGB(57, 181, 74), HSL(0.36, 0.52, 0.47)
22
+
23
+ // GRAYS
24
+ // - Sorted by lightness , from dark to light
25
+ $color-gray-anthracite: #26292C; // RGB(38, 41, 44) HSL(0.58, 0.07, 0.16) $theme-dark-dark $eps-dark-gray-800
26
+ $color-gray-metalise: #34383c; // RGB(52, 56, 60) HSL(0.58, 0.07, 0.22) $theme-dark-gray-darkest $eps-dark-gray-700
27
+ $color-gray-napoleon: #404349; // RGB(64, 67, 73) HSL(0.61, 0.07, 0.27) $theme-dark-gray-dark $eps-dark-gray-600
28
+ $color-gray-lamp-post: #495157; // RGB(73, 81, 87) HSL(0.57, 0.09, 0.31) $editor-darkest $eps-gray-800
29
+ $color-gray-abbey: #4c4f56; // RGB(76, 79, 86) HSL(0.62, 0.06, 0.32) $theme-dark-gray $eps-dark-gray-500
30
+ $color-gray-blue-planet: #556068; // RGB(85, 96, 104) HSL(0.57, 0.10, 0.37) $editor-darker $eps-gray-700
31
+ $color-gray-stone-hearth: #64666a; // RGB(100, 102, 106) HSL(0.61, 0.03, 0.40) $theme-dark-gray-light $eps-dark-gray-400
32
+ $color-gray-sheffield: #6d7882; // RGB(109, 120, 130) HSL(0.58, 0.09, 0.47) $editor-dark $eps-gray-600
33
+ $color-gray-silver-filigree: #7d7e82; // RGB(125, 126, 130) HSL(0.63, 0.02, 0.50) $theme-dark-gray-lighter $eps-dark-gray-300
34
+ $color-gray-special-delivery: #a4afb7; // RGB(164, 175, 183) HSL(0.57, 0.12, 0.68) $editor-light $eps-gray-500
35
+ $color-gray-brainstem: #b4b5b7; // RGB(180, 181, 183) HSL(0.61, 0.02, 0.71) $theme-dark-gray-lightest $eps-dark-gray-200
36
+ $color-gray-stone-golem: #c2cbd2; // RGB(194, 203, 210) HSL(0.57, 0.15, 0.79) $editor-lighter $eps-gray-400
37
+ $color-gray-hidden-creek: #d5dadf; // RGB(213, 218, 223) HSL(0.58, 0.14, 0.85) $editor-lightest $eps-gray-300
38
+ $color-gray-yin-bai-silver: #e0e1e3; // RGB(224, 225, 227) HSL(0.61, 0.05, 0.88) $theme-dark-light $eps-dark-gray-100
39
+ $color-gray-anti-flash: #f1f3f5; // RGB(241, 243, 245) HSL(0.58, 0.17, 0.95) $editor-background-light $eps-gray-200
40
+ $color-gray-emptiness: #fcfcfc; // RGB(252, 252, 252) HSL(0, 0, 0.99) $editor-background-lighter $eps-gray-100
app/assets/styles/tokens/_font.scss ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Font
2
+ //$eps-font-weight-thin: 200;
3
+ //$eps-font-weight-light: 300;
4
+ $eps-font-weight-normal: 400;
5
+ $eps-font-weight-medium: 500;
6
+ $eps-font-weight-bold: 700;
7
+ $eps-font-weight-base: $eps-font-weight-normal;
8
+
9
+ //font face names
10
+ $font-src-roboto: 'roboto';
11
+
12
+ // generic, check if exist
13
+ $font-family-monospace: monospace;
14
+ $font-family-roboto: Roboto;
15
+ $font-family-arial: Arial;
16
+ $font-family-helvetica: Helvetica;
17
+ $font-family-verdana: Verdana;
18
+
19
+ $eps-font-family-code: $font-family-monospace;
20
+ $eps-font-family-fallback: sans-serif;
21
+ $eps-font-family-base: $font-family-roboto, $font-family-arial, $font-family-helvetica, $font-family-verdana;
22
+ $eps-font-family: $eps-font-family-base, $eps-font-family-fallback;
app/assets/styles/tokens/_global.scss ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ $eps-prefix: prefix(eps);
2
+
3
+ $eps-link-decoration: none;
4
+ $eps-link-hover-decoration: none;
5
+
6
+ $eps-hover-state-operator: 5%;
7
+ $eps-active-state-operator: 5%;
app/assets/styles/tokens/_map-functions.scss ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Tokens: map functions
2
+ // - Simple 'get' mechanizm hardcoded to scoped maps.
3
+ @function spacing($keys...) {
4
+ @return map-deep-get($eps-spacers, $keys...);
5
+ }
6
+
7
+ // Below functions can be duplicated and modified for Themes
8
+ @function theme-colors($keys...) {
9
+ @return map-deep-get($eps-theme-colors, $keys...);
10
+ }
11
+
12
+ @function tints($keys...) {
13
+ @return map-deep-get($eps-tints, $keys...);
14
+ }
15
+
16
+ @function theme-elements-colors($keys...) {
17
+ @return map-deep-get($eps-theme-elements-colors, $keys...);
18
+ }
19
+
20
+ @function type($keys...) {
21
+ @return map-deep-get($eps-type-map, $keys...);
22
+ }
23
+
24
+ @function z-index($keys...) {
25
+ @return map-deep-get($eps-z-index-levels, $keys...);
26
+ }
27
+
28
+ @function opacity($keys...) {
29
+ @return map-deep-get($eps-opacity-map, $keys...);
30
+ }
31
+
32
+ @function shadow($keys...) {
33
+ @return map-deep-get($eps-shadow-map, $keys...);
34
+ }
35
+
36
+ @function atomizer($keys...){
37
+ @return map-deep-get($eps-atoms,$keys...);
38
+ };
app/assets/styles/tokens/_opacity.scss ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ $opacity-08: 0.8;
2
+ $opacity-07: 0.7;
3
+ $opacity-05: 0.5;
4
+ $opacity-03: 0.3;
5
+ $opacity-02: 0.2;
6
+ $opacity-015: 0.15;
7
+ $opacity-01: 0.1;
8
+ $opacity-005: 0.05;
app/assets/styles/tokens/_ratio.scss ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Ratio
2
+
3
+ $ratio-portrait: 0.956 * 100%;
4
+ $eps-ratio-16-9: calc( 100% / 16 ) * 9;
5
+
6
+ $embed-responsive-aspect-ratios: () !default;
7
+ // stylelint-disable-next-line scss/dollar-variable-default
8
+ $embed-responsive-aspect-ratios: join( (
9
+ (21 9),
10
+ (16 9),
11
+ (4 3),
12
+ (1 1),
13
+ ), $embed-responsive-aspect-ratios );
app/assets/styles/tokens/_spacing.scss ADDED
@@ -0,0 +1 @@
 
1
+ $eps-spacer: 0.5rem;
app/assets/styles/tokens/_theme.scss ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Default, Light Theme
2
+ // - Gray Semantics
3
+ $eps-gray-800: $color-gray-lamp-post;
4
+ $eps-gray-700: $color-gray-blue-planet;
5
+ $eps-gray-600: $color-gray-sheffield;
6
+ $eps-gray-500: $color-gray-special-delivery;
7
+ $eps-gray-400: $color-gray-stone-golem;
8
+ $eps-gray-300: $color-gray-hidden-creek;
9
+ $eps-gray-200: $color-gray-anti-flash;
10
+ $eps-gray-100: $color-gray-emptiness;
11
+
12
+ // Theme semantics
13
+ // - data context
14
+ $eps-theme-text-muted: $eps-gray-300;
15
+ $eps-theme-disabled: $eps-gray-400;
16
+ //
17
+ $eps-theme-light: $color-white;
18
+ $eps-theme-dark: $color-black;
19
+ //
20
+ $eps-theme-info: $color-cyan-ionized-air-glow;
21
+ $eps-theme-accent: $color-cyan-ionized-air-glow;
22
+ $eps-theme-danger: $color-red-carnelian;
23
+ $eps-theme-cta: $color-red-rose-garnet;
24
+ $eps-theme-warning: $color-yellow-hot-sun;
25
+ $eps-theme-success: $color-green-spandex;
26
+
27
+ // -use context
28
+ $eps-body-color: $eps-gray-600;
29
+ $eps-body-bg: null;
30
+ $eps-link-color: $color-cyan-ionized-air-glow;
31
+ $eps-link-hover-color: darken($eps-link-color, 15%);
32
+ $eps-hr-color: $eps-gray-300;
33
+ $eps-display-1-color: $eps-gray-800;
34
+ $eps-display-2-color: $eps-gray-800;
35
+ $eps-display-3-color: $eps-gray-600;
36
+ $eps-display-4-color: $eps-gray-800;
37
+ $eps-h1-color: $eps-gray-600;
38
+ $eps-h2-color: $eps-gray-600;
39
+ $eps-h3-color: $eps-gray-800;
40
+ $eps-h4-color: $eps-gray-800;
41
+ $eps-h5-color: $eps-gray-800;
42
+ $eps-h6-color: $eps-gray-800;
43
+ $eps-text-base-color: $eps-gray-800;
44
+ $eps-text-xl-color: $eps-gray-800;
45
+ $eps-text-lg-color: $eps-gray-800;
46
+ $eps-text-sm-color: $eps-gray-800;
47
+ $eps-text-xs-color: $eps-gray-800;
48
+ $eps-text-xxs-color: $eps-gray-800;
app/assets/styles/tokens/_type.scss ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // TYPE
2
+ $eps-font-size-base: 1rem;
3
+
4
+ // Font sizes registration
5
+ $font-size-10: (0.625 * $eps-font-size-base); //~10px
6
+ $font-size-11: (0.6875 * $eps-font-size-base); //~11px
7
+ $font-size-12: (0.75 * $eps-font-size-base); //~12px
8
+ $font-size-13: (0.8125 * $eps-font-size-base); //~13px
9
+ $font-size-14: (0.875 * $eps-font-size-base); //~14px
10
+ $font-size-15: (0.9375 * $eps-font-size-base); //~15px
11
+ $font-size-18: (1.125 * $eps-font-size-base); //~18px
12
+ $font-size-20: (1.25 * $eps-font-size-base); //~20px
13
+ $font-size-26: (1.625 * $eps-font-size-base); //~26
14
+ $font-size-30: (1.85 * $eps-font-size-base); //~30px
15
+
16
+ // Line heights
17
+ // Registration (temps)
18
+ $line-height-2: 2; // [$card-headline-line-height,];
19
+
20
+ // Semantics
21
+ $eps-line-height-flat: 1; //can be hardcoded as 1
22
+ $eps-line-height-base: 1.5;
23
+ $eps-line-height-sm: 1.2;
24
+ $eps-line-height-lg: 1.8;
25
+
26
+ // scale
27
+ $eps-display-1-font-size: $font-size-30;
28
+ $eps-display-2-font-size: $font-size-30;
29
+ $eps-display-3-font-size: $font-size-30;
30
+ $eps-display-4-font-size: $font-size-30;
31
+ //
32
+ $eps-h-1-font-size: $font-size-26;
33
+ $eps-h-2-font-size: $font-size-20;
34
+ $eps-h-3-font-size: $eps-font-size-base;
35
+ $eps-h-4-font-size: $font-size-15;
36
+ $eps-h-5-font-size: $font-size-14;
37
+ $eps-h-6-font-size: $font-size-14;
38
+
39
+ $eps-text-xl-font-size: $eps-font-size-base;//16px
40
+ $eps-text-lg-font-size: $font-size-15;
41
+ $eps-text-md-font-size: $font-size-14;
42
+ $eps-text-sm-font-size: $font-size-13;
43
+ $eps-text-xs-font-size: $font-size-12;
44
+ $eps-text-xxs-font-size: $font-size-11;
45
+ $eps-font-size-micro: $font-size-10; //10px
46
+
47
+ $eps-body-font-size: $eps-text-md-font-size;
48
+ $eps-font-size-caption: $eps-text-xs-font-size;
49
+ //
50
+
51
+ $eps-small-font-size: 80%;
52
+ $eps-paragraph-margin-bottom: null;
53
+ $eps-headings-margin-bottom: null;
app/assets/styles/tokens/_z-index.scss ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ // Z-INDEX
2
+ $eps-zindex-dropdown: 1000;
3
+ $eps-zindex-sticky: 1010;
4
+ $eps-zindex-fixed: 1020;
5
+ $eps-zindex-modal-backdrop: 1030;
6
+ $eps-zindex-modal: 1040;
7
+ $eps-zindex-popover: 1050;
8
+ $eps-zindex-tooltip: 1060;
app/assets/styles/tokens/maps/_atoms.scss ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-atoms: (
2
+ typography:(
3
+ display:(
4
+ 1:(
5
+ font-size: $eps-display-1-font-size,
6
+ line-height:null,
7
+ color: var(--display-1-color),
8
+ margin-top: $eps-spacer,
9
+ margin-bottom: $eps-spacer,
10
+ font-familiy:null,
11
+ font-weight:null,
12
+ text-transform: null,
13
+ ),
14
+ 2:(
15
+ font-size: $eps-display-2-font-size,
16
+ line-height:null,
17
+ color: var(--display-2-color),
18
+ margin-top: $eps-spacer,
19
+ margin-bottom: $eps-spacer,
20
+ font-familiy:null,
21
+ font-weight:null,
22
+ font-style: null,
23
+ text-transform: null,
24
+ ),
25
+ 3:(
26
+ font-size: $eps-display-3-font-size,
27
+ line-height:null,
28
+ color: var(--display-3-color),
29
+ margin-top: 0,
30
+ margin-bottom: 2.5 * $eps-spacer,
31
+ font-familiy:null,
32
+ font-weight:null,
33
+ font-style: null,
34
+ text-transform: null,
35
+ ),
36
+ 4:(
37
+ font-size: $eps-display-4-font-size,
38
+ line-height:null,
39
+ color: var(--display-4-color),
40
+ margin-top: $eps-spacer,
41
+ margin-bottom: $eps-spacer,
42
+ font-familiy:null,
43
+ font-weight:null,
44
+ font-style: null,
45
+ text-transform: null,
46
+ ),
47
+ ),
48
+ heading:(
49
+ 1:(
50
+ font-size: $eps-h-1-font-size,
51
+ line-height: $eps-line-height-flat,
52
+ color: var(--h1-color),
53
+ margin-top: null,
54
+ margin-bottom: 2.5 * $eps-spacer,
55
+ font-familiy:null,
56
+ font-weight: $eps-font-weight-medium,
57
+ font-style: null,
58
+ text-transform: null,
59
+ ),
60
+ 2:(
61
+ font-size: $eps-h-2-font-size,
62
+ line-height: $eps-line-height-sm,
63
+ color: var(--h2-color),
64
+ margin-top: 0,
65
+ margin-bottom: 2.5 * $eps-spacer,
66
+ font-familiy: null,
67
+ font-weight: $eps-font-weight-medium,
68
+ font-style: null,
69
+ text-transform: null,
70
+ ),
71
+ 3:(
72
+ font-size: $eps-h-3-font-size,
73
+ line-height: $eps-line-height-sm,
74
+ color: var(--h3-color),
75
+ margin-top: 0,
76
+ margin-bottom: $eps-spacer,
77
+ font-familiy:null,
78
+ font-weight:$eps-font-weight-medium,
79
+ font-style: null,
80
+ text-transform: null,
81
+ ),
82
+ 4:(
83
+ font-size: $eps-h-4-font-size,
84
+ line-height:null,
85
+ color: var(--h4-color),
86
+ margin-top: 0,
87
+ margin-bottom: $eps-spacer,
88
+ font-familiy:null,
89
+ font-weight:null,
90
+ font-style: null,
91
+ text-transform: null,
92
+ ),
93
+ 5:(
94
+ font-size: $eps-h-5-font-size,
95
+ line-height:null,
96
+ color: var(--h5-color),
97
+ margin-top: 0,
98
+ margin-bottom: $eps-spacer,
99
+ font-familiy:null,
100
+ font-weight:null,
101
+ text-transform: null,
102
+ ),
103
+ 6:(
104
+ font-size: $eps-h-6-font-size,
105
+ line-height:null,
106
+ color: var(--h-6-color),
107
+ margin-top: 0,
108
+ margin-bottom: $eps-spacer,
109
+ font-familiy:null,
110
+ font-weight:$eps-font-weight-bold,
111
+ text-transform: null,
112
+ ),
113
+ ),
114
+ text:(
115
+ base:(
116
+ font-size:$eps-body-font-size,
117
+ line-height:$eps-line-height-base,
118
+ color: var(--text-base-color),
119
+ margin-top: null,
120
+ margin-bottom:null,
121
+ font-familiy:null,
122
+ font-weight:$eps-font-weight-normal,
123
+ ),
124
+ xl:(
125
+ font-size:$eps-text-xl-font-size,
126
+ line-height:$eps-line-height-base,
127
+ color: var(--text-xl-color),
128
+ margin-top: null,
129
+ margin-bottom:null,
130
+ font-familiy:null,
131
+ font-weight:$eps-font-weight-normal,
132
+ ),
133
+ lg:(
134
+ font-size:$eps-text-lg-font-size,
135
+ line-height:$eps-line-height-base,
136
+ color: var(--text-lg-color),
137
+ margin-top: null,
138
+ margin-bottom:null,
139
+ font-familiy:null,
140
+ font-weight:$eps-font-weight-normal,
141
+ ),
142
+ md:(
143
+ font-size:$eps-text-md-font-size,
144
+ line-height:$eps-line-height-base,
145
+ color: var(--text-md-color),
146
+ margin-top: null,
147
+ margin-bottom:null,
148
+ font-familiy:null,
149
+ font-weight:$eps-font-weight-normal,
150
+ ),
151
+ sm:(
152
+ font-size:$eps-text-sm-font-size,
153
+ line-height:$eps-line-height-base,
154
+ color: var(--text-sm-color),
155
+ margin-top: null,
156
+ margin-bottom:null,
157
+ font-familiy:null,
158
+ font-weight:$eps-font-weight-normal,
159
+ ),
160
+ xs:(
161
+ font-size:$eps-text-xs-font-size,
162
+ line-height:$eps-line-height-base,
163
+ color: var(--text-xs-color),
164
+ margin-top: null,
165
+ margin-bottom:null,
166
+ font-familiy:null,
167
+ font-weight:$eps-font-weight-normal,
168
+ ),
169
+ xxs:(
170
+ font-size:$eps-text-xs-font-size,
171
+ line-height:$eps-line-height-sm,
172
+ color: var(--text-xxs-color),
173
+ margin-top: null,
174
+ margin-bottom:null,
175
+ font-familiy:null,
176
+ font-weight:$eps-font-weight-normal,
177
+ ),
178
+ ),
179
+ )
180
+ );
181
+
app/assets/styles/tokens/maps/_opacity-map.scss ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-opacity-map: (
2
+ "1": 1,
3
+ "08": $opacity-08,
4
+ "05": $opacity-05,
5
+ "03": $opacity-03,
6
+ "02": $opacity-02,
7
+ "015": $opacity-015,
8
+ "01": $opacity-01,
9
+ "005":$opacity-005,
10
+ "0": 0,
11
+ );
app/assets/styles/tokens/maps/_shadow-map.scss ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ $eps-shadow-map: (
2
+ "1": $eps-box-shadow,
3
+ "2": $eps-box-shadow-2,
4
+ );
app/assets/styles/tokens/maps/_spacing-map.scss ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-spacers: (
2
+ 0: 0,
3
+ 5: $eps-spacer * 0.625,
4
+ 8: $eps-spacer,
5
+ 10: $eps-spacer * 1.25,
6
+ 12: $eps-spacer * 1.5,
7
+ 16: $eps-spacer * 2,
8
+ 20: $eps-spacer * 2.5,
9
+ 24: $eps-spacer * 3,
10
+ 30: $eps-spacer * 3.75,
11
+ 44: $eps-spacer * 5.5,
12
+ );
app/assets/styles/tokens/maps/_theme-map.scss ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-theme-colors: (
2
+ text-muted:$eps-theme-text-muted,
3
+ disabled: $eps-theme-disabled,
4
+ light: $eps-theme-light,
5
+ dark: $eps-theme-dark,
6
+ info: $eps-theme-info,
7
+ cta: $eps-theme-cta,
8
+ danger: $eps-theme-danger,
9
+ success: $eps-theme-success,
10
+ warning: $eps-theme-warning,
11
+ );
12
+
13
+ $eps-theme-elements-colors: (
14
+ body-color: $eps-body-color,
15
+ body-bg:$eps-body-bg,
16
+ link-color: $eps-link-color,
17
+ link-hover-color: $eps-link-hover-color,
18
+ hr-color: $eps-hr-color,
19
+ box-shadow-color: $eps-box-shadow-color,
20
+ display-1-color:$eps-display-1-color,
21
+ display-2-color:$eps-display-2-color,
22
+ display-3-color:$eps-display-3-color,
23
+ display-4-color:$eps-display-4-color,
24
+ h1-color:$eps-h1-color,
25
+ h2-color:$eps-h2-color,
26
+ h3-color:$eps-h3-color,
27
+ h4-color:$eps-h4-color,
28
+ h5-color:$eps-h5-color,
29
+ h6-color:$eps-h6-color,
30
+ text-base-color:$eps-text-base-color,
31
+ text-xl-color:$eps-text-xl-color,
32
+ text-lg-color:$eps-text-lg-color,
33
+ text-sm-color:$eps-text-sm-color,
34
+ text-xs-color:$eps-text-xs-color,
35
+ text-xxs-color:$eps-text-xxs-color,
36
+ )
app/assets/styles/tokens/maps/_tints-map.scss ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ // Maps
2
+ $eps-tints: (
3
+ 800: $eps-gray-800,
4
+ 700: $eps-gray-700,
5
+ 600: $eps-gray-600,
6
+ 500: $eps-gray-500,
7
+ 400: $eps-gray-400,
8
+ 300: $eps-gray-300,
9
+ 200: $eps-gray-200,
10
+ 100: $eps-gray-100,
11
+ );
app/assets/styles/tokens/maps/_type-map.scss ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $eps-type-map: (
2
+ size:(
3
+ base: $eps-font-size-base,
4
+ "10": $font-size-10,
5
+ "11": $font-size-11,
6
+ "12": $font-size-12,
7
+ "13": $font-size-13,
8
+ "14": $font-size-14,
9
+ "15": $font-size-15,
10
+ "18": $font-size-18,
11
+ "20": $font-size-20,
12
+ "26": $font-size-26,
13
+ "30": $font-size-30,
14
+ ),
15
+ line-height:(
16
+ "2":$line-height-2,
17
+ flat:$eps-line-height-flat,
18
+ base:$eps-line-height-base,
19
+ sm:$eps-line-height-sm,
20
+ lg:$eps-line-height-lg,
21
+ ),
22
+ display:(
23
+ "1":$eps-display-1-font-size,
24
+ "2":$eps-display-2-font-size,
25
+ "3":$eps-display-3-font-size,
26
+ "4":$eps-display-4-font-size,
27
+ ),
28
+ h:(
29
+ "1":$eps-h-1-font-size,
30
+ "2":$eps-h-2-font-size,
31
+ "3":$eps-h-3-font-size,
32
+ "4":$eps-h-4-font-size,
33
+ "5":$eps-h-5-font-size,
34
+ "6":$eps-h-6-font-size,
35
+ ),
36
+ text:(
37
+ base:$eps-font-size-base,
38
+ xxs:$eps-text-xxs-font-size,
39
+ xs:$eps-text-xs-font-size,
40
+ sm:$eps-text-sm-font-size,
41
+ md:$eps-text-md-font-size,
42
+ lg:$eps-text-lg-font-size,
43
+ xl:$eps-text-xl-font-size,
44
+ ),
45
+ caption: $eps-font-size-caption,
46
+ micro: $eps-font-size-micro,
47
+ );
app/assets/styles/tokens/maps/_z-index-map.scss ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ // Maps: z-index map
2
+ $eps-z-index-levels: (
3
+ dropdown:$eps-zindex-dropdown,
4
+ sticky: $eps-zindex-sticky,
5
+ fixed:$eps-zindex-fixed,
6
+ backdrop: $eps-zindex-modal-backdrop,
7
+ modal: $eps-zindex-modal,
8
+ popover: $eps-zindex-popover,
9
+ tooltip: $eps-zindex-tooltip,
10
+ );
app/assets/styles/tokens/styles/_border.scss ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // EDS border tokens
2
+ $border-style-solid: solid;
3
+ $border-style-dashed: dashed;
4
+ $border-style-dotted: dotted;
5
+
6
+ $eps-border-style: $border-style-solid;
7
+ $eps-border-width: 1px;
8
+ $eps-border-width-md: 2px;
9
+ $eps-border-width-lg: 3px;
10
+
11
+ $eps-border-color: $eps-gray-200;
12
+ $eps-border-color-light: lighten($eps-border-color, 10%);
13
+ $eps-border-color-dark: darken($eps-border-color, 5%);
14
+
15
+ $eps-border: $eps-border-width $eps-border-style $eps-border-color;
app/assets/styles/tokens/styles/_motion.scss ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ // Motion
2
+
3
+ $duration-03s: 0.3s;
4
+
5
+ //
6
+ $eps-transition-duration: $duration-03s;
app/assets/styles/tokens/styles/_radius.scss ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ // EDS radius tokens
2
+ $eps-radius: .1875rem;//~3px
3
+ $eps-radius-sm: .125rem;//~2px
4
+ //$eps-radius-md: .375rem;//~6px
5
+ //$eps-radius-lg: .5rem;//~8px
6
+ $eps-radius-pill: 50rem;
7
+ $eps-radius-round: 50%;
app/assets/styles/tokens/styles/_shadow.scss ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // TOKENS STYLES: SHADOW presets
2
+
3
+ $eps-box-shadow-alpha-unit: 0.05;
4
+
5
+ $eps-box-shadow-color: theme-colors(dark);
6
+ $eps-box-shadow-size: 0 3px 6px;
7
+ $eps-box-shadow-size-1: 0 2px 3px 1px;
8
+ $eps-box-shadow-size-2: 0 4px 10px;
9
+ $eps-box-shadow-size-3: 2px 8px 23px 3px;
10
+
11
+ $eps-box-shadow-alpha-unit-dark: 0.15;
12
+
13
+ :root {
14
+ --color-box-shadow-color: rgba(0, 0, 0, 0.05);
15
+ }
16
+
17
+ .eps-theme-dark {
18
+ --color-box-shadow-color: rgba(0, 0, 0, 0.1);
19
+ }
20
+
21
+ // shadow presets
22
+ // todo: temp values, map to semantics when semantics (use casing) is concluded.
23
+ $eps-box-shadow: $eps-box-shadow-size var(--color-box-shadow-color);
24
+ $eps-box-shadow-1: $eps-box-shadow-size-1 var(--color-box-shadow-color);
25
+ $eps-box-shadow-2: $eps-box-shadow-size-2 var(--color-box-shadow-color);
26
+ $eps-box-shadow-3: $eps-box-shadow-size-3 var(--color-box-shadow-color);
app/modules/import-export/assets/js/admin.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const revertButton = document.getElementById( 'elementor-import-export__revert_kit' );
2
+
3
+ if ( revertButton ) {
4
+ revertButton.addEventListener( 'click', ( event ) => {
5
+ event.preventDefault();
6
+
7
+ elementorCommon.dialogsManager.createWidget( 'confirm', {
8
+ headerMessage: __( 'Sure you want to make these changes?', 'elementor' ),
9
+ message: __( 'Removing assets or changing your site settings can drastically change the look of your website.', 'elementor' ),
10
+ strings: {
11
+ confirm: __( 'Yes', 'elementor' ),
12
+ cancel: __( 'No, go back', 'elementor' ),
13
+ },
14
+ onConfirm() {
15
+ location.href = revertButton.href;
16
+ },
17
+ } ).show();
18
+ } );
19
+ }
app/modules/import-export/assets/js/context/export-context/export-context-provider.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useReducer } from 'react';
2
+
3
+ import { reducer } from './export-context-reducer';
4
+
5
+ export const ExportContext = React.createContext();
6
+
7
+ export default function ExportContextProvider( props ) {
8
+ const initialState = {
9
+ downloadUrl: '',
10
+ exportedData: null,
11
+ isExportProcessStarted: false,
12
+ plugins: [],
13
+ kitInfo: {
14
+ title: null,
15
+ description: null,
16
+ },
17
+ },
18
+ [ data, dispatch ] = useReducer( reducer, initialState );
19
+
20
+ return (
21
+ <ExportContext.Provider value={ { data, dispatch } }>
22
+ { props.children }
23
+ </ExportContext.Provider>
24
+ );
25
+ }
26
+
27
+ ExportContextProvider.propTypes = {
28
+ children: PropTypes.object.isRequired,
29
+ };
app/modules/import-export/assets/js/context/export-context/export-context-reducer.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const reducer = ( state, { type, payload } ) => {
2
+ switch ( type ) {
3
+ case 'SET_DOWNLOAD_URL':
4
+ return { ...state, downloadUrl: payload };
5
+ case 'SET_EXPORTED_DATA':
6
+ return { ...state, exportedData: payload };
7
+ case 'SET_PLUGINS':
8
+ return { ...state, plugins: payload };
9
+ case 'SET_IS_EXPORT_PROCESS_STARTED':
10
+ return { ...state, isExportProcessStarted: payload };
11
+ case 'SET_KIT_TITLE':
12
+ return { ...state, kitInfo: { ...state.kitInfo, title: payload } };
13
+ case 'SET_KIT_DESCRIPTION':
14
+ return { ...state, kitInfo: { ...state.kitInfo, description: payload } };
15
+ default:
16
+ return state;
17
+ }
18
+ };
app/modules/import-export/assets/js/context/import-context/import-context-provider.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useReducer } from 'react';
2
+
3
+ import { reducer } from './import-context-reducer';
4
+
5
+ export const ImportContext = React.createContext();
6
+
7
+ export default function ImportContextProvider( props ) {
8
+ const initialState = {
9
+ file: null,
10
+ uploadedData: null,
11
+ importedData: null,
12
+ plugins: [],
13
+ requiredPlugins: [],
14
+ importedPlugins: [],
15
+ overrideConditions: [],
16
+ isProInstalledDuringProcess: false,
17
+ actionType: null,
18
+ isResolvedData: false,
19
+ pluginsState: '',
20
+ },
21
+ [ data, dispatch ] = useReducer( reducer, initialState );
22
+
23
+ return (
24
+ <ImportContext.Provider value={ { data, dispatch } }>
25
+ { props.children }
26
+ </ImportContext.Provider>
27
+ );
28
+ }
29
+
30
+ ImportContextProvider.propTypes = {
31
+ children: PropTypes.object.isRequired,
32
+ };
app/modules/import-export/assets/js/context/import-context/import-context-reducer.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ReducerUtils } from '../utils/reducer-utils';
2
+
3
+ export const reducer = ( state, { type, payload } ) => {
4
+ switch ( type ) {
5
+ case 'SET_FILE':
6
+ return { ...state, file: payload };
7
+ case 'ADD_OVERRIDE_CONDITION':
8
+ return ReducerUtils.updateArray( state, 'overrideConditions', payload, 'add' );
9
+ case 'REMOVE_OVERRIDE_CONDITION':
10
+ return ReducerUtils.updateArray( state, 'overrideConditions', payload, 'remove' );
11
+ case 'SET_UPLOADED_DATA':
12
+ return { ...state, uploadedData: payload };
13
+ case 'SET_IMPORTED_DATA':
14
+ return { ...state, importedData: payload };
15
+ case 'SET_PLUGINS':
16
+ return { ...state, plugins: payload };
17
+ case 'SET_REQUIRED_PLUGINS':
18
+ return { ...state, requiredPlugins: payload };
19
+ case 'SET_IMPORTED_PLUGINS':
20
+ return { ...state, importedPlugins: payload };
21
+ case 'SET_IS_PRO_INSTALLED_DURING_PROCESS':
22
+ return { ...state, isProInstalledDuringProcess: payload };
23
+ case 'SET_ACTION_TYPE':
24
+ return { ...state, actionType: payload };
25
+ case 'SET_IS_RESOLVED':
26
+ return { ...state, isResolvedData: payload };
27
+ case 'SET_PLUGINS_STATE':
28
+ return { ...state, pluginsState: payload };
29
+ default:
30
+ return state;
31
+ }
32
+ };
app/modules/import-export/assets/js/context/shared-context/shared-context-provider.js ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useReducer } from 'react';
2
+
3
+ import { reducer } from './shared-context-reducer';
4
+
5
+ import kitContentData from '../../shared/kit-content-data/kit-content-data';
6
+
7
+ export const SharedContext = React.createContext();
8
+
9
+ export default function SharedContextProvider( props ) {
10
+ const initialState = {
11
+ includes: kitContentData.map( ( item ) => item.type ),
12
+ referrer: null,
13
+ customPostTypes: [],
14
+ selectedCustomPostTypes: null,
15
+ currentPage: null,
16
+ },
17
+ [ data, dispatch ] = useReducer( reducer, initialState );
18
+
19
+ return (
20
+ <SharedContext.Provider value={ { data, dispatch } }>
21
+ { props.children }
22
+ </SharedContext.Provider>
23
+ );
24
+ }
25
+
26
+ SharedContextProvider.propTypes = {
27
+ children: PropTypes.object.isRequired,
28
+ };
app/modules/import-export/assets/js/context/shared-context/shared-context-reducer.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ReducerUtils } from '../utils/reducer-utils';
2
+
3
+ export const reducer = ( state, { type, payload } ) => {
4
+ switch ( type ) {
5
+ case 'ADD_INCLUDE':
6
+ return ReducerUtils.updateArray( state, 'includes', payload, 'add' );
7
+ case 'REMOVE_INCLUDE':
8
+ return ReducerUtils.updateArray( state, 'includes', payload, 'remove' );
9
+ case 'SET_REFERRER':
10
+ return { ...state, referrer: payload };
11
+ case 'SET_INCLUDES':
12
+ return { ...state, includes: payload };
13
+ case 'SET_CPT':
14
+ return { ...state, customPostTypes: payload };
15
+ case 'SET_SELECTED_CPT':
16
+ return { ...state, selectedCustomPostTypes: payload };
17
+ case 'SET_CURRENT_PAGE_NAME':
18
+ return { ...state, currentPage: payload };
19
+ default:
20
+ return state;
21
+ }
22
+ };
app/modules/import-export/assets/js/context/utils/reducer-utils.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export class ReducerUtils {
2
+ static updateArray( state, key, value, action ) {
3
+ if ( 'add' === action ) {
4
+ // If the value already exists, then do nothing.
5
+ if ( state[ key ].includes( value ) ) {
6
+ return state;
7
+ }
8
+
9
+ return { ...state, [ key ]: [ ...state[ key ], value ] };
10
+ } else if ( 'remove' === action ) {
11
+ return { ...state, [ key ]: state[ key ].filter( ( item ) => item !== value ) };
12
+ }
13
+
14
+ return state;
15
+ }
16
+ }
app/modules/import-export/assets/js/export.js ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import SharedContextProvider from './context/shared-context/shared-context-provider';
2
+ import ExportContextProvider from './context/export-context/export-context-provider';
3
+
4
+ import { LocationProvider, Router } from '@reach/router';
5
+ import router from '@elementor/router';
6
+
7
+ import ExportKit from './pages/export/export-kit/export-kit';
8
+ import ExportComplete from './pages/export/export-complete/export-complete';
9
+ import ExportPlugins from './pages/export/export-plugins/export-plugins';
10
+ import ExportProcess from './pages/export/export-process/export-process';
11
+
12
+ export default function Export() {
13
+ return (
14
+ <SharedContextProvider>
15
+ <ExportContextProvider>
16
+ <LocationProvider history={ router.appHistory }>
17
+ <Router>
18
+ <ExportComplete path="complete" />
19
+ <ExportPlugins path="plugins" />
20
+ <ExportProcess path="process" />
21
+ <ExportKit default />
22
+ </Router>
23
+ </LocationProvider>
24
+ </ExportContextProvider>
25
+ </SharedContextProvider>
26
+ );
27
+ }
app/modules/import-export/assets/js/hooks/use-kit.js ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react';
2
+
3
+ import useAjax from 'elementor-app/hooks/use-ajax';
4
+
5
+ const KIT_STATUS_MAP = Object.freeze( {
6
+ INITIAL: 'initial',
7
+ UPLOADED: 'uploaded',
8
+ IMPORTED: 'imported',
9
+ EXPORTED: 'exported',
10
+ ERROR: 'error',
11
+ } ),
12
+ UPLOAD_KIT_KEY = 'elementor_upload_kit',
13
+ IMPORT_KIT_KEY = 'elementor_import_kit',
14
+ EXPORT_KIT_KEY = 'elementor_export_kit';
15
+
16
+ export default function useKit() {
17
+ const { ajaxState, setAjax, ajaxActions } = useAjax(),
18
+ kitStateInitialState = {
19
+ status: KIT_STATUS_MAP.INITIAL,
20
+ data: null,
21
+ },
22
+ [ kitState, setKitState ] = useState( kitStateInitialState ),
23
+ uploadKit = ( { file, kitLibraryNonce } ) => {
24
+ setAjax( {
25
+ data: {
26
+ action: UPLOAD_KIT_KEY,
27
+ e_import_file: file,
28
+ ...( kitLibraryNonce ? { e_kit_library_nonce: kitLibraryNonce } : {} ),
29
+ },
30
+ } );
31
+ },
32
+ importKit = ( { session, include, overrideConditions, referrer, selectedCustomPostTypes } ) => {
33
+ const ajaxConfig = {
34
+ data: {
35
+ action: IMPORT_KIT_KEY,
36
+ data: {
37
+ session,
38
+ include,
39
+ overrideConditions,
40
+ },
41
+ },
42
+ };
43
+
44
+ if ( referrer ) {
45
+ ajaxConfig.data.data.referrer = referrer;
46
+ }
47
+
48
+ if ( selectedCustomPostTypes ) {
49
+ ajaxConfig.data.data.selectedCustomPostTypes = selectedCustomPostTypes;
50
+ }
51
+
52
+ ajaxConfig.data.data = JSON.stringify( ajaxConfig.data.data );
53
+
54
+ setAjax( ajaxConfig );
55
+ },
56
+ exportKit = ( { include, kitInfo, plugins, selectedCustomPostTypes } ) => {
57
+ setAjax( {
58
+ data: {
59
+ action: EXPORT_KIT_KEY,
60
+ data: JSON.stringify( {
61
+ include,
62
+ kitInfo,
63
+ plugins,
64
+ selectedCustomPostTypes,
65
+ } ),
66
+ },
67
+ } );
68
+ },
69
+ reset = () => ajaxActions.reset();
70
+
71
+ useEffect( () => {
72
+ if ( 'initial' !== ajaxState.status ) {
73
+ const newState = {};
74
+
75
+ if ( 'success' === ajaxState.status ) {
76
+ if ( ajaxState.response?.file ) {
77
+ newState.status = KIT_STATUS_MAP.EXPORTED;
78
+ } else {
79
+ newState.status = ajaxState.response?.manifest ? KIT_STATUS_MAP.UPLOADED : KIT_STATUS_MAP.IMPORTED;
80
+ }
81
+ } else if ( 'error' === ajaxState.status ) {
82
+ newState.status = KIT_STATUS_MAP.ERROR;
83
+ }
84
+
85
+ // The response is required even if an error occurred, in order to detect the error type.
86
+ newState.data = ajaxState.response || {};
87
+
88
+ setKitState( ( prevState ) => ( { ...prevState, ...newState } ) );
89
+ }
90
+ }, [ ajaxState.status ] );
91
+
92
+ return {
93
+ kitState,
94
+ KIT_STATUS_MAP,
95
+ kitActions: {
96
+ upload: uploadKit,
97
+ import: importKit,
98
+ export: exportKit,
99
+ reset,
100
+ },
101
+ };
102
+ }
app/modules/import-export/assets/js/hooks/use-plugins-data.js ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMemo } from 'react';
2
+
3
+ export const PLUGINS_KEYS = Object.freeze( {
4
+ ELEMENTOR: 'Elementor',
5
+ ELEMENTOR_PRO: 'Elementor Pro',
6
+ } );
7
+
8
+ export default function usePluginsData( plugins ) {
9
+ const getPluginsData = () => {
10
+ if ( ! plugins ) {
11
+ return [];
12
+ }
13
+
14
+ const elementorPlugins = [],
15
+ generalPlugins = [];
16
+
17
+ plugins.forEach( ( plugin ) => {
18
+ switch ( plugin.name ) {
19
+ case PLUGINS_KEYS.ELEMENTOR:
20
+ // Making sure that the core plugin is always first.
21
+ elementorPlugins.unshift( plugin );
22
+ break;
23
+ case PLUGINS_KEYS.ELEMENTOR_PRO:
24
+ // Making sure that the pro plugin is always second.
25
+ elementorPlugins.push( plugin );
26
+ break;
27
+ default:
28
+ generalPlugins.push( plugin );
29
+ }
30
+ } );
31
+
32
+ // Making sure that the elementor plugins are always first.
33
+ return elementorPlugins.concat( generalPlugins );
34
+ };
35
+
36
+ return {
37
+ pluginsData: useMemo( () => getPluginsData(), [ plugins ] ),
38
+ };
39
+ }
app/modules/import-export/assets/js/hooks/use-plugins.js ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useRef } from 'react';
2
+
3
+ export const PLUGINS_RESPONSE_MAP = Object.freeze( {
4
+ INITIAL: 'initial',
5
+ SUCCESS: 'success',
6
+ ERROR: 'error',
7
+ } );
8
+
9
+ export const PLUGIN_STATUS_MAP = Object.freeze( {
10
+ ACTIVE: 'active',
11
+ MULTISITE_ACTIVE: 'network-active',
12
+ INACTIVE: 'inactive',
13
+ NOT_INSTALLED: 'Not Installed',
14
+ } );
15
+
16
+ const baseEndpoint = elementorCommon.config.urls.rest + 'wp/v2/plugins/',
17
+ getInitialState = () => ( {
18
+ status: PLUGINS_RESPONSE_MAP.INITIAL,
19
+ data: null,
20
+ } );
21
+
22
+ export default function usePlugins() {
23
+ const [ response, setResponse ] = useState( () => getInitialState() ),
24
+ allowResponseUpdate = useRef( true ),
25
+ fetchRest = ( { body, method, endpoint = '' } ) => {
26
+ const data = {
27
+ method,
28
+ headers: {
29
+ 'Content-Type': 'application/json; charset=utf-8',
30
+ 'X-WP-Nonce': wpApiSettings.nonce,
31
+ },
32
+ };
33
+
34
+ if ( body ) {
35
+ data.body = JSON.stringify( body );
36
+ }
37
+
38
+ if ( response.data ) {
39
+ reset();
40
+ }
41
+
42
+ return new Promise( ( resolve, reject ) => {
43
+ fetch( baseEndpoint + endpoint, data )
44
+ .then( ( res ) => res.json() )
45
+ .then( ( res ) => {
46
+ if ( allowResponseUpdate.current ) {
47
+ setResponse( {
48
+ status: PLUGINS_RESPONSE_MAP.SUCCESS,
49
+ data: res,
50
+ } );
51
+ }
52
+
53
+ resolve( res );
54
+ } )
55
+ .catch( ( error ) => {
56
+ setResponse( {
57
+ status: PLUGINS_RESPONSE_MAP.ERROR,
58
+ data: error,
59
+ } );
60
+
61
+ reject( error );
62
+ } );
63
+ } );
64
+ },
65
+ fetchData = ( slug ) => {
66
+ return fetchRest( {
67
+ method: 'GET',
68
+ endpoint: slug,
69
+ } );
70
+ },
71
+ install = ( slug ) => {
72
+ slug = slug.split( '/' )[ 0 ];
73
+
74
+ return fetchRest( {
75
+ method: 'POST',
76
+ body: {
77
+ slug,
78
+ },
79
+ } );
80
+ },
81
+ activate = ( slug ) => {
82
+ return fetchRest( {
83
+ endpoint: slug,
84
+ method: 'PUT',
85
+ body: {
86
+ status: PLUGIN_STATUS_MAP.ACTIVE,
87
+ },
88
+ } );
89
+ },
90
+ deactivate = ( slug ) => {
91
+ return fetchRest( {
92
+ endpoint: slug,
93
+ method: 'PUT',
94
+ body: {
95
+ status: PLUGIN_STATUS_MAP.INACTIVE,
96
+ },
97
+ } );
98
+ },
99
+ remove = ( slug ) => {
100
+ return fetchRest( {
101
+ endpoint: slug,
102
+ method: 'DELETE',
103
+ } );
104
+ },
105
+ reset = () => setResponse( getInitialState() );
106
+
107
+ // On load.
108
+ useEffect( () => {
109
+ fetchData();
110
+
111
+ // Cleanup on destroy.
112
+ return () => {
113
+ allowResponseUpdate.current = false;
114
+ };
115
+ }, [] );
116
+
117
+ return {
118
+ response,
119
+ pluginsActions: {
120
+ fetch: fetchData,
121
+ install,
122
+ activate,
123
+ deactivate,
124
+ remove,
125
+ reset,
126
+ },
127
+ };
128
+ }
app/modules/import-export/assets/js/import.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import SharedContextProvider from './context/shared-context/shared-context-provider';
2
+ import ImportContextProvider from './context/import-context/import-context-provider';
3
+
4
+ import { LocationProvider, Router } from '@reach/router';
5
+ import router from '@elementor/router';
6
+
7
+ import ImportKit from './pages/import/import-kit/import-kit';
8
+ import ImportContent from './pages/import/import-content/import-content';
9
+ import ImportResolver from './pages/import/import-resolver/import-resolver';
10
+ import ImportPluginsActivation from './pages/import/import-plugins-activation/import-plugins-activation';
11
+ import ImportProcess from './pages/import/import-process/import-process';
12
+ import ImportComplete from './pages/import/import-complete/import-complete';
13
+ import ImportPlugins from './pages/import/import-plugins/import-plugins';
14
+
15
+ export default function Import() {
16
+ return (
17
+ <SharedContextProvider>
18
+ <ImportContextProvider>
19
+ <LocationProvider history={ router.appHistory }>
20
+ <Router>
21
+ <ImportComplete path="complete" />
22
+ <ImportProcess path="process" />
23
+ <ImportResolver path="resolver" />
24
+ <ImportContent path="content" />
25
+ <ImportPlugins path="plugins" />
26
+ <ImportPluginsActivation path="plugins-activation" />
27
+ <ImportKit default />
28
+ </Router>
29
+ </LocationProvider>
30
+ </ImportContextProvider>
31
+ </SharedContextProvider>
32
+ );
33
+ }
app/modules/import-export/assets/js/module.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import router from '@elementor/router';
2
+
3
+ import Import from './import';
4
+ import Export from './export';
5
+
6
+ export default class ImportExport {
7
+ routes = [
8
+ {
9
+ path: '/import/*',
10
+ component: Import,
11
+ },
12
+ {
13
+ path: '/export/*',
14
+ component: Export,
15
+ },
16
+ ];
17
+
18
+ constructor() {
19
+ for ( const route of this.routes ) {
20
+ router.addRoute( route );
21
+ }
22
+ }
23
+ }
app/modules/import-export/assets/js/package.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ // Alphabetical order.
2
+ import Layout from './templates/layout';
3
+ import Module from './module';
4
+
5
+ export default {
6
+ Layout,
7
+ Module,
8
+ };
app/modules/import-export/assets/js/pages/export/export-complete/export-complete.js ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext, useEffect, useRef } from 'react';
2
+ import { useNavigate } from '@reach/router';
3
+
4
+ import { ExportContext } from '../../../context/export-context/export-context-provider';
5
+
6
+ import Layout from '../../../templates/layout';
7
+ import ActionsFooter from '../../../shared/actions-footer/actions-footer';
8
+ import WizardStep from '../../../ui/wizard-step/wizard-step';
9
+ import KitData from '../../../shared/kit-data/kit-data';
10
+ import InlineLink from 'elementor-app/ui/molecules/inline-link';
11
+ import DashboardButton from 'elementor-app/molecules/dashboard-button';
12
+
13
+ import './export-complete.scss';
14
+
15
+ export default function ExportComplete() {
16
+ const exportContext = useContext( ExportContext ),
17
+ navigate = useNavigate(),
18
+ downloadLink = useRef( null ),
19
+ getFooter = () => (
20
+ <ActionsFooter>
21
+ <DashboardButton text={ __( 'Close', 'elementor' ) } />
22
+ </ActionsFooter>
23
+ ),
24
+ downloadFile = () => {
25
+ if ( ! downloadLink.current ) {
26
+ const link = document.createElement( 'a' );
27
+
28
+ link.href = 'data:text/plain;base64,' + exportContext.data.exportedData.file;
29
+ link.download = 'elementor-kit.zip';
30
+
31
+ downloadLink.current = link;
32
+ }
33
+
34
+ downloadLink.current.click();
35
+ },
36
+ getDownloadLink = () => (
37
+ <InlineLink onClick={ downloadFile } italic>
38
+ { __( 'Click here', 'elementor' ) }
39
+ </InlineLink>
40
+ );
41
+
42
+ useEffect( () => {
43
+ if ( exportContext.data.exportedData ) {
44
+ downloadFile();
45
+ } else {
46
+ navigate( '/export' );
47
+ }
48
+ }, [ exportContext.data.downloadUrl ] );
49
+
50
+ return (
51
+ <Layout type="export" footer={ getFooter() }>
52
+ <WizardStep
53
+ image={ elementorAppConfig.assets_url + 'images/go-pro.svg' }
54
+ heading={ __( 'Your export is ready!', 'elementor' ) }
55
+ description={ __( 'Now you can import this kit and use it on other sites.', 'elementor' ) }
56
+ notice={ (
57
+ <>
58
+ { __( 'Download not working?', 'elementor' ) } { getDownloadLink() } { __( 'to download', 'elementor' ) }
59
+ </>
60
+ ) }
61
+ >
62
+ <KitData data={ exportContext.data?.exportedData?.manifest } />
63
+ </WizardStep>
64
+ </Layout>
65
+ );
66
+ }
app/modules/import-export/assets/js/pages/export/export-complete/export-complete.scss ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ .e-app-export-complete {
2
+ &__kit-content-title {
3
+ margin: spacing(44) 0 spacing(10);
4
+ }
5
+ }
app/modules/import-export/assets/js/pages/export/export-kit/components/kit-information/components/kit-description/kit-description.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+
3
+ import { ExportContext } from '../../../../../../../context/export-context/export-context-provider';
4
+
5
+ import TextField from 'elementor-app/ui/atoms/text-field';
6
+
7
+ export default function KitDescription() {
8
+ const exportContext = useContext( ExportContext );
9
+
10
+ return (
11
+ <TextField
12
+ variant="outlined"
13
+ // eslint-disable-next-line @wordpress/i18n-ellipsis
14
+ placeholder={ __( 'Say something about the style and content of these files...', 'elementor' ) }
15
+ multiline
16
+ rows={ 5 }
17
+ onChange={ ( event ) => {
18
+ exportContext.dispatch( { type: 'SET_KIT_DESCRIPTION', payload: event.target.value } );
19
+ } }
20
+ />
21
+ );
22
+ }
app/modules/import-export/assets/js/pages/export/export-kit/components/kit-information/components/kit-info-modal/kit-info-modal.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ModalProvider from 'elementor-app/ui/modal/modal';
2
+ import Heading from 'elementor-app/ui/atoms/heading';
3
+ import Text from 'elementor-app/ui/atoms/text';
4
+
5
+ export default function KitInfoModal( props ) {
6
+ return (
7
+ <ModalProvider { ...props } className="e-app-export-kit-info-modal" title={ __( 'Website Kit Information', 'elementor' ) }>
8
+ <ModalProvider.Section>
9
+ <Heading className="e-app-export-kit-info-modal__heading" variant="h2" tag="h3">
10
+ { __( 'What is kit information?', 'elementor' ) }
11
+ </Heading>
12
+ <Text>
13
+ { __( 'These are the details you’ll use to quickly find and apply this kit in the future, even as your collection grows.', 'elementor' ) }
14
+ </Text>
15
+ </ModalProvider.Section>
16
+ </ModalProvider>
17
+ );
18
+ }
app/modules/import-export/assets/js/pages/export/export-kit/components/kit-information/components/kit-name/kit-name.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+
3
+ import { ExportContext } from '../../../../../../../context/export-context/export-context-provider';
4
+
5
+ import TextField from 'elementor-app/ui/atoms/text-field';
6
+
7
+ export default function KitName() {
8
+ const exportContext = useContext( ExportContext );
9
+
10
+ return (
11
+ <TextField
12
+ variant="outlined"
13
+ placeholder={ __( 'Elementor Kit', 'elementor' ) }
14
+ onChange={ ( event ) => {
15
+ exportContext.dispatch( { type: 'SET_KIT_TITLE', payload: event.target.value } );
16
+ } }
17
+ />
18
+ );
19
+ }
app/modules/import-export/assets/js/pages/export/export-kit/components/kit-information/kit-information.js ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+
3
+ import KitName from './components/kit-name/kit-name';
4
+ import KitDescription from './components/kit-description/kit-description';
5
+ import KitInfoModal from './components/kit-info-modal/kit-info-modal';
6
+
7
+ import Panel from 'elementor-app/ui/panel/panel';
8
+ import Grid from 'elementor-app/ui/grid/grid';
9
+ import Heading from 'elementor-app/ui/atoms/heading';
10
+ import Button from 'elementor-app/ui/molecules/button';
11
+
12
+ const kitInfoTitle = __( 'Kit Information', 'elementor' );
13
+
14
+ export default function KitInformation() {
15
+ const [ showKitInfoModal, setShowKitInfoModal ] = useState( false );
16
+
17
+ return (
18
+ <>
19
+ <Panel className="e-app-export-kit-information">
20
+ <Panel.Header>
21
+ <Panel.Headline>
22
+ { kitInfoTitle }
23
+
24
+ <Button
25
+ className="e-app-export-kit-info-modal__icon"
26
+ icon="eicon-info-circle"
27
+ color="secondary"
28
+ hideText={ true }
29
+ text={ kitInfoTitle }
30
+ onClick={ ( event ) => {
31
+ event.stopPropagation();
32
+
33
+ setShowKitInfoModal( ( prevState ) => ! prevState );
34
+ } }
35
+ />
36
+ </Panel.Headline>
37
+ </Panel.Header>
38
+
39
+ <Panel.Body>
40
+ <Grid container spacing={ 20 }>
41
+ <Grid item md={ 4 }>
42
+ <Grid container direction="column">
43
+ <Grid className="e-app-export-kit-information__field-header" container alignItems="center">
44
+ <Heading className="e-app-export-kit-information__label" variant="h6" tag="h4">
45
+ { __( 'Kit Name', 'elementor' ) }
46
+ </Heading>
47
+ </Grid>
48
+
49
+ <Grid item>
50
+ <KitName />
51
+ </Grid>
52
+ </Grid>
53
+ </Grid>
54
+
55
+ <Grid item md={ 4 }>
56
+ <Grid className="e-app-export-kit-information__field-header" container alignItems="center">
57
+ <Heading className="e-app-export-kit-information__label" variant="h6" tag="h4">
58
+ { __( 'Kit Description', 'elementor' ) }
59
+ </Heading>
60
+ </Grid>
61
+
62
+ <Grid item>
63
+ <KitDescription />
64
+ </Grid>
65
+ </Grid>
66
+ </Grid>
67
+ </Panel.Body>
68
+ </Panel>
69
+
70
+ <KitInfoModal show={ showKitInfoModal } setShow={ setShowKitInfoModal } />
71
+ </>
72
+ );
73
+ }
app/modules/import-export/assets/js/pages/export/export-kit/export-kit.js ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect, useContext } from 'react';
2
+
3
+ import { ExportContext } from '../../../context/export-context/export-context-provider';
4
+ import { SharedContext } from '../../../context/shared-context/shared-context-provider';
5
+
6
+ import { cptObjectToOptionsArray } from '../../../shared/cpt-select-box/cpt-object-to-options-array';
7
+ import Layout from '../../../templates/layout';
8
+ import PageHeader from '../../../ui/page-header/page-header';
9
+ import KitContent from '../../../shared/kit-content/kit-content';
10
+ import KitInformation from './components/kit-information/kit-information';
11
+ import ActionsFooter from '../../../shared/actions-footer/actions-footer';
12
+ import InlineLink from 'elementor-app/ui/molecules/inline-link';
13
+ import Button from 'elementor-app/ui/molecules/button';
14
+
15
+ import kitContentData from '../../../shared/kit-content-data/kit-content-data';
16
+
17
+ import './export-kit.scss';
18
+
19
+ export default function ExportKit() {
20
+ const exportContext = useContext( ExportContext ),
21
+ sharedContext = useContext( SharedContext ),
22
+ getFooter = () => (
23
+ <ActionsFooter>
24
+ <Button
25
+ variant="contained"
26
+ text={ __( 'Next', 'elementor' ) }
27
+ color="primary"
28
+ url="/export/plugins"
29
+ />
30
+ </ActionsFooter>
31
+ ),
32
+ getLearnMoreLink = () => (
33
+ <InlineLink url="https://go.elementor.com/app-what-are-kits" italic>
34
+ { __( 'Learn More', 'elementor' ) }
35
+ </InlineLink>
36
+ );
37
+
38
+ useEffect( () => {
39
+ exportContext.dispatch( { type: 'SET_IS_EXPORT_PROCESS_STARTED', payload: true } );
40
+ sharedContext.dispatch( { type: 'SET_CPT', payload: cptObjectToOptionsArray( elementorAppConfig[ 'import-export' ].summaryTitles.content?.customPostTypes, 'plural' ) } );
41
+ }, [] );
42
+
43
+ return (
44
+ <Layout type="export" footer={ getFooter() }>
45
+ <section className="e-app-export-kit">
46
+ <PageHeader
47
+ heading={ __( 'Export a Website Kit', 'elementor' ) }
48
+ description={ [
49
+ __( 'Choose which Elementor components - templates, content and site settings - to include in your kit file.', 'elementor' ),
50
+ <React.Fragment key="description-secondary-line">
51
+ { __( 'By default, all of your components will be exported.', 'elementor' ) } { getLearnMoreLink() }
52
+ </React.Fragment>,
53
+ ] }
54
+ />
55
+
56
+ <KitContent contentData={ kitContentData } />
57
+
58
+ <KitInformation />
59
+ </section>
60
+ </Layout>
61
+ );
62
+ }
app/modules/import-export/assets/js/pages/export/export-kit/export-kit.scss ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $root: e-app-export-kit;
2
+
3
+ .#{$root} {
4
+ padding-bottom: spacing(20);
5
+
6
+ &-information {
7
+ margin-top: spacing(20);
8
+
9
+ &__field-header {
10
+ margin-bottom: spacing(10);
11
+ }
12
+
13
+ &__label {
14
+ margin: 0;
15
+ }
16
+
17
+ &__info-icon {
18
+ margin-inline-start: spacing(10);
19
+ }
20
+ }
21
+
22
+ &-info-modal {
23
+ &__icon {
24
+ padding-inline-start: spacing(10);
25
+ }
26
+
27
+ &__heading {
28
+ margin-bottom: spacing(20);
29
+ }
30
+ }
31
+ }
32
+
33
+ .eps-theme-dark {
34
+ .#{$root} {
35
+ }
36
+ }
app/modules/import-export/assets/js/pages/export/export-plugins/components/export-plugins-footer/export-plugins-footer.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ActionsFooter from '../../../../../shared/actions-footer/actions-footer';
2
+ import Button from 'elementor-app/ui/molecules/button';
3
+
4
+ export default function ExportPluginsFooter( { isKitReady } ) {
5
+ return (
6
+ <ActionsFooter>
7
+ <Button
8
+ text={ __( 'Back', 'elementor' ) }
9
+ variant="contained"
10
+ url="/export"
11
+ />
12
+
13
+ <Button
14
+ text={ __( 'Create Kit', 'elementor' ) }
15
+ variant="contained"
16
+ color={ isKitReady ? 'primary' : 'disabled' }
17
+ url={ isKitReady ? '/export/process' : '' }
18
+ />
19
+ </ActionsFooter>
20
+ );
21
+ }
22
+
23
+ ExportPluginsFooter.propTypes = {
24
+ isKitReady: PropTypes.bool.isRequired,
25
+ };
app/modules/import-export/assets/js/pages/export/export-plugins/components/export-plugins-selection/export-plugins-selection.js ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { memo } from 'react';
2
+
3
+ import PluginsSelection from '../../../../../shared/plugins-selection/plugins-selection';
4
+ import Loader from '../../../../../ui/loader/loader';
5
+
6
+ import usePlugins, { PLUGIN_STATUS_MAP } from '../../../../../hooks/use-plugins';
7
+ import usePluginsData, { PLUGINS_KEYS } from '../../../../../hooks/use-plugins-data';
8
+
9
+ const layout = [ 3, 1 ],
10
+ initialDisabled = [ 0 ]; // Elementor Core will always be first and should always be disabled.
11
+
12
+ function ExportPluginsSelection( { onSelect } ) {
13
+ const { response } = usePlugins(),
14
+ { pluginsData } = usePluginsData( response.data ),
15
+ activePlugins = pluginsData.filter( ( { status } ) => PLUGIN_STATUS_MAP.ACTIVE === status || PLUGIN_STATUS_MAP.MULTISITE_ACTIVE === status ),
16
+ getInitialSelected = () => {
17
+ // Elementor Core will always be the first plugin on the list.
18
+ const initialSelected = [ 0 ];
19
+
20
+ // In case that Elementor Pro appears in the list it will always be second and should always be selected by default.
21
+ if ( activePlugins.length > 1 && PLUGINS_KEYS.ELEMENTOR_PRO === activePlugins[ 1 ].name ) {
22
+ initialSelected.push( 1 );
23
+ }
24
+
25
+ return initialSelected;
26
+ };
27
+
28
+ if ( ! response.data ) {
29
+ return <Loader absoluteCenter />;
30
+ }
31
+
32
+ return (
33
+ <PluginsSelection
34
+ plugins={ activePlugins }
35
+ initialSelected={ getInitialSelected() }
36
+ initialDisabled={ initialDisabled }
37
+ layout={ layout }
38
+ withStatus={ false }
39
+ onSelect={ onSelect }
40
+ />
41
+ );
42
+ }
43
+
44
+ ExportPluginsSelection.propTypes = {
45
+ onSelect: PropTypes.func.isRequired,
46
+ };
47
+
48
+ export default memo( ExportPluginsSelection );
app/modules/import-export/assets/js/pages/export/export-plugins/export-plugins.js ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useContext, useEffect, useCallback } from 'react';
2
+ import { useNavigate } from '@reach/router';
3
+
4
+ import { SharedContext } from '../../../context/shared-context/shared-context-provider';
5
+ import { ExportContext } from '../../../context/export-context/export-context-provider';
6
+
7
+ import Layout from '../../../templates/layout';
8
+ import PageHeader from '../../../ui/page-header/page-header';
9
+
10
+ import ExportPluginsSelection from './components/export-plugins-selection/export-plugins-selection';
11
+ import ExportPluginsFooter from './components/export-plugins-footer/export-plugins-footer';
12
+
13
+ import './export-plugins.scss';
14
+
15
+ export default function ExportPlugins() {
16
+ const sharedContext = useContext( SharedContext ),
17
+ exportContext = useContext( ExportContext ),
18
+ navigate = useNavigate(),
19
+ [ isKitReady, setIsKitReady ] = useState( false ),
20
+ { plugins, isExportProcessStarted } = exportContext.data || [],
21
+ hasIncludes = ! ! sharedContext.data.includes.length,
22
+ handleOnSelect = useCallback( ( selectedPlugins ) => exportContext.dispatch( { type: 'SET_PLUGINS', payload: selectedPlugins } ), [] );
23
+
24
+ // On load.
25
+ useEffect( () => {
26
+ if ( ! isExportProcessStarted ) {
27
+ // When not starting from the main screen.
28
+ navigate( '/export' );
29
+ }
30
+ }, [] );
31
+
32
+ // On plugins change.
33
+ useEffect( () => {
34
+ if ( hasIncludes && plugins.length ) {
35
+ // In case that the kit has content and the plugins data exist, then the kit can be exported.
36
+ setIsKitReady( true );
37
+ } else {
38
+ // There should be at least one more plugin select in addition to Elementor Core.
39
+ const isExportKitAllowed = plugins.length > 1;
40
+
41
+ // In case that the kit has no content, it can only be exported if there is at least one selected plugin.
42
+ setIsKitReady( isExportKitAllowed );
43
+ }
44
+ }, [ plugins ] );
45
+
46
+ return (
47
+ <Layout type="export" footer={ <ExportPluginsFooter isKitReady={ isKitReady } /> }>
48
+ <section className="e-app-export-plugins">
49
+ <PageHeader
50
+ heading={ __( 'Export your site as a Website Kit', 'elementor' ) }
51
+ description={ __( 'Select which of these plugins are required for this kit work.', 'elementor' ) }
52
+ />
53
+
54
+ <ExportPluginsSelection onSelect={ handleOnSelect } />
55
+ </section>
56
+ </Layout>
57
+ );
58
+ }
app/modules/import-export/assets/js/pages/export/export-plugins/export-plugins.scss ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ .e-app-export-plugins {
2
+ padding-bottom: spacing(20);
3
+ }
app/modules/import-export/assets/js/pages/export/export-process/export-process.js ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useContext, useState } from 'react';
2
+ import { useNavigate } from '@reach/router';
3
+
4
+ import { SharedContext } from '../../../context/shared-context/shared-context-provider';
5
+ import { ExportContext } from '../../../context/export-context/export-context-provider';
6
+
7
+ import Layout from '../../../templates/layout';
8
+ import FileProcess from '../../../shared/file-process/file-process';
9
+
10
+ import useKit from '../../../hooks/use-kit';
11
+ import useExportPluginsData from './hooks/use-export-plugins-data';
12
+
13
+ export default function ExportProcess() {
14
+ const sharedContext = useContext( SharedContext ),
15
+ exportContext = useContext( ExportContext ),
16
+ navigate = useNavigate(),
17
+ { kitState, kitActions, KIT_STATUS_MAP } = useKit(),
18
+ [ errorType, setErrorType ] = useState( '' ),
19
+ { plugins, exportedData, kitInfo, isExportProcessStarted } = exportContext.data || {},
20
+ { pluginsData } = useExportPluginsData( plugins ),
21
+ onDialogDismiss = () => {
22
+ exportContext.dispatch( { type: 'SET_DOWNLOAD_URL', payload: '' } );
23
+ navigate( 'export' );
24
+ },
25
+ exportKit = () => {
26
+ const { includes, selectedCustomPostTypes } = sharedContext.data;
27
+
28
+ /*
29
+ Adding the plugins just before the export process begins for not mixing the kit-content selection with the plugins.
30
+ The plugins must be added to the includes items, otherwise they will not be exported.
31
+ The plugins should always be added in order to include the Core plugin data in the kit.
32
+ */
33
+ kitActions.export( {
34
+ include: [ ...includes, 'plugins' ],
35
+ kitInfo,
36
+ plugins: pluginsData,
37
+ selectedCustomPostTypes,
38
+ } );
39
+ };
40
+
41
+ // On load.
42
+ useEffect( () => {
43
+ if ( isExportProcessStarted ) {
44
+ exportKit();
45
+ } else {
46
+ // When not starting from the main screen.
47
+ navigate( '/export' );
48
+ }
49
+ }, [] );
50
+
51
+ // On kit status change.
52
+ useEffect( () => {
53
+ switch ( kitState.status ) {
54
+ case KIT_STATUS_MAP.EXPORTED:
55
+ exportContext.dispatch( { type: 'SET_EXPORTED_DATA', payload: kitState.data } );
56
+ break;
57
+ case KIT_STATUS_MAP.ERROR:
58
+ setErrorType( kitState.data );
59
+ break;
60
+ }
61
+ }, [ kitState.status ] );
62
+
63
+ // On process finished.
64
+ useEffect( () => {
65
+ if ( exportedData ) {
66
+ navigate( 'export/complete' );
67
+ }
68
+ }, [ exportedData ] );
69
+
70
+ return (
71
+ <Layout type="export">
72
+ <FileProcess
73
+ errorType={ errorType }
74
+ onDialogApprove={ onDialogDismiss }
75
+ onDialogDismiss={ onDialogDismiss }
76
+ />
77
+ </Layout>
78
+ );
79
+ }
app/modules/import-export/assets/js/pages/export/export-process/hooks/use-export-plugins-data.js ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMemo } from 'react';
2
+
3
+ export default function useExportPluginsData( plugins ) {
4
+ const getData = () => {
5
+ const pluginsData = [];
6
+
7
+ plugins.forEach( ( pluginData ) => {
8
+ const { name, plugin, plugin_uri: pluginUri, version } = pluginData;
9
+
10
+ pluginsData.push( {
11
+ name,
12
+ plugin,
13
+ pluginUri,
14
+ version,
15
+ } );
16
+ } );
17
+
18
+ return pluginsData;
19
+ };
20
+
21
+ return {
22
+ pluginsData: useMemo( () => getData(), [ plugins ] ),
23
+ };
24
+ }
app/modules/import-export/assets/js/pages/import/hooks/use-import-actions.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+ import { useNavigate } from '@reach/router';
3
+
4
+ import { SharedContext } from '../../../context/shared-context/shared-context-provider';
5
+
6
+ import useAction from 'elementor-app/hooks/use-action';
7
+
8
+ export default function useImportActions() {
9
+ const sharedContext = useContext( SharedContext ),
10
+ navigate = useNavigate(),
11
+ { backToDashboard } = useAction(),
12
+ isStartedFromKitLibrary = 'kit-library' === sharedContext.data.referrer,
13
+ navigateToMainScreen = () => {
14
+ const url = isStartedFromKitLibrary ? '/kit-library' : '/import';
15
+
16
+ navigate( url );
17
+ },
18
+ closeApp = () => {
19
+ if ( isStartedFromKitLibrary ) {
20
+ navigate( '/kit-library' );
21
+ } else {
22
+ backToDashboard();
23
+ }
24
+ };
25
+
26
+ return {
27
+ navigateToMainScreen,
28
+ closeApp,
29
+ };
30
+ }
app/modules/import-export/assets/js/pages/import/import-complete/components/connect-pro-notice/connect-pro-notice.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Notice from 'elementor-app/ui/molecules/notice';
2
+ import Button from 'elementor-app/ui/molecules/button';
3
+
4
+ import './connect-pro-notice.scss';
5
+
6
+ export default function ConnectProNotice() {
7
+ const getButton = () => (
8
+ <Button
9
+ text={ __( 'Let’s do it', 'elementor' ) }
10
+ variant="outlined"
11
+ color="secondary"
12
+ size="sm"
13
+ target="_blank"
14
+ url={ elementorAppConfig.admin_url + 'admin.php?page=elementor-license' }
15
+ />
16
+ );
17
+
18
+ return (
19
+ <Notice className="e-app-import-connect-pro-notice" label={ __( 'Tip:', 'elementor' ) } color="info" button={ getButton() }>
20
+ { __( 'Make sure your Elementor Pro account is connected', 'elementor' ) }
21
+ </Notice>
22
+ );
23
+ }
app/modules/import-export/assets/js/pages/import/import-complete/components/connect-pro-notice/connect-pro-notice.scss ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ .e-app-import-connect-pro-notice {
2
+ margin-bottom: spacing(20);
3
+ }
app/modules/import-export/assets/js/pages/import/import-complete/components/failed-plugins-notice/failed-plugins-notice.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Notice from 'elementor-app/ui/molecules/notice';
2
+ import Button from 'elementor-app/ui/molecules/button';
3
+
4
+ import './failed-plugins-notice.scss';
5
+
6
+ export default function FailedPluginsNotice( { failedPlugins } ) {
7
+ const getButton = () => (
8
+ <Button
9
+ text={ __( 'Learn more', 'elementor' ) }
10
+ variant="outlined"
11
+ color="secondary"
12
+ size="sm"
13
+ target="_blank"
14
+ url="https://go.elementor.com/app-import-plugin-installation-failed/"
15
+ />
16
+ );
17
+
18
+ return (
19
+ <Notice className="e-app-import-failed-plugins-notice" label={ __( 'Important:', 'elementor' ) } color="warning" button={ getButton() }>
20
+ {
21
+ __( "There are few plugins that we couldn't install:", 'elementor' ) + ' ' +
22
+ failedPlugins.map( ( { name } ) => name ).join( ' | ' )
23
+ }
24
+ </Notice>
25
+ );
26
+ }
27
+
28
+ FailedPluginsNotice.propTypes = {
29
+ failedPlugins: PropTypes.array,
30
+ };
app/modules/import-export/assets/js/pages/import/import-complete/components/failed-plugins-notice/failed-plugins-notice.scss ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ .e-app-import-failed-plugins-notice {
2
+ margin-bottom: spacing(20);
3
+ }
app/modules/import-export/assets/js/pages/import/import-complete/components/import-complete-footer/import-complete-footer.js ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ActionsFooter from '../../../../../shared/actions-footer/actions-footer';
2
+ import Button from 'elementor-app/ui/molecules/button';
3
+ import useAction from 'elementor-app/hooks/use-action';
4
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
5
+
6
+ export default function ImportCompleteFooter( { seeItLiveUrl, referrer } ) {
7
+ const action = useAction(),
8
+ eventTracking = ( command, eventType = 'click' ) => {
9
+ if ( 'kit-library' === referrer ) {
10
+ appsEventTrackingDispatch(
11
+ command,
12
+ {
13
+ page_source: 'kit is live',
14
+ element_location: 'app_wizard_footer',
15
+ event_type: eventType,
16
+ },
17
+ );
18
+ }
19
+ };
20
+
21
+ return (
22
+ <ActionsFooter>
23
+ {
24
+ seeItLiveUrl &&
25
+ <Button
26
+ text={ __( 'See it live', 'elementor' ) }
27
+ variant="contained"
28
+ onClick={ () => {
29
+ eventTracking( 'kit-library/see-it-live' );
30
+ window.open( seeItLiveUrl, '_blank' );
31
+ } }
32
+ />
33
+ }
34
+
35
+ <Button
36
+ text={ __( 'Close', 'elementor' ) }
37
+ variant="contained"
38
+ color="primary"
39
+ onClick={ () => {
40
+ eventTracking( 'kit-library/close' );
41
+ action.backToDashboard();
42
+ } }
43
+ />
44
+ </ActionsFooter>
45
+ );
46
+ }
47
+
48
+ ImportCompleteFooter.propTypes = {
49
+ seeItLiveUrl: PropTypes.string,
50
+ referrer: PropTypes.string,
51
+ };
app/modules/import-export/assets/js/pages/import/import-complete/hooks/use-imported-kit-data.js ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { PLUGIN_STATUS_MAP } from '../../../../hooks/use-plugins';
2
+
3
+ export default function useImportedKitData() {
4
+ const getTemplates = ( templates, importedData ) => {
5
+ const kitTemplates = {};
6
+
7
+ for ( const key in importedData?.templates?.succeed ) {
8
+ kitTemplates[ key ] = templates[ key ];
9
+ }
10
+
11
+ return kitTemplates;
12
+ },
13
+ getContent = ( content, importedData ) => {
14
+ const kitContent = {};
15
+
16
+ for ( const contentType in importedData?.content ) {
17
+ kitContent[ contentType ] = {};
18
+
19
+ for ( const key in importedData.content[ contentType ]?.succeed ) {
20
+ kitContent[ contentType ][ key ] = content[ contentType ][ key ];
21
+ }
22
+ }
23
+
24
+ return kitContent;
25
+ },
26
+ getWPContent = ( content, importedData ) => {
27
+ const kitWPContent = {};
28
+
29
+ for ( const contentType in importedData?.[ 'wp-content' ] ) {
30
+ const succeededItems = importedData[ 'wp-content' ][ contentType ]?.succeed;
31
+
32
+ kitWPContent[ contentType ] = succeededItems ? Object.keys( succeededItems ) : [];
33
+ }
34
+
35
+ return kitWPContent;
36
+ },
37
+ getPlugins = ( importedPlugins ) => {
38
+ const plugins = {
39
+ activePlugins: [],
40
+ failedPlugins: [],
41
+ };
42
+
43
+ importedPlugins.forEach( ( plugin ) => {
44
+ const group = PLUGIN_STATUS_MAP.ACTIVE === plugin.status ? 'activePlugins' : 'failedPlugins';
45
+
46
+ plugins[ group ].push( plugin );
47
+ } );
48
+
49
+ return plugins;
50
+ };
51
+
52
+ return {
53
+ getTemplates,
54
+ getContent,
55
+ getWPContent,
56
+ getPlugins,
57
+ };
58
+ }
app/modules/import-export/assets/js/pages/import/import-complete/import-complete.js ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext, useEffect, useMemo } from 'react';
2
+ import { useNavigate } from '@reach/router';
3
+
4
+ import { SharedContext } from '../../../context/shared-context/shared-context-provider';
5
+ import { ImportContext } from '../../../context/import-context/import-context-provider';
6
+
7
+ import Layout from '../../../templates/layout';
8
+ import WizardStep from '../../../ui/wizard-step/wizard-step';
9
+ import KitData from '../../../shared/kit-data/kit-data';
10
+ import InlineLink from 'elementor-app/ui/molecules/inline-link';
11
+
12
+ import FailedPluginsNotice from './components/failed-plugins-notice/failed-plugins-notice';
13
+ import ConnectProNotice from './components/connect-pro-notice/connect-pro-notice';
14
+ import ImportCompleteFooter from './components/import-complete-footer/import-complete-footer';
15
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
16
+
17
+ import useImportedKitData from './hooks/use-imported-kit-data';
18
+
19
+ export default function ImportComplete() {
20
+ const sharedContext = useContext( SharedContext ),
21
+ importContext = useContext( ImportContext ),
22
+ navigate = useNavigate(),
23
+ { importedPlugins, uploadedData, importedData, isProInstalledDuringProcess } = importContext.data || {},
24
+ { referrer } = sharedContext.data || {},
25
+ { getTemplates, getContent, getWPContent, getPlugins } = useImportedKitData(),
26
+ { activePlugins, failedPlugins } = getPlugins( importedPlugins ),
27
+ { elementorHomePageUrl, recentlyEditedElementorPageUrl } = importedData?.configData || {},
28
+ seeItLiveUrl = elementorHomePageUrl || recentlyEditedElementorPageUrl || null,
29
+ getKitData = () => {
30
+ if ( ! uploadedData || ! importedData ) {
31
+ return {};
32
+ }
33
+
34
+ const manifest = uploadedData.manifest;
35
+
36
+ return {
37
+ templates: getTemplates( manifest.templates, importedData ),
38
+ content: getContent( manifest.content, importedData ),
39
+ 'wp-content': getWPContent( manifest[ 'wp-content' ], importedData ),
40
+ 'site-settings': sharedContext.data.includes.includes( 'settings' ) ? manifest[ 'site-settings' ] : {},
41
+ plugins: activePlugins,
42
+ configData: importedData.configData,
43
+ };
44
+ },
45
+ eventTracking = ( command, source, eventType = 'click', elementLocation = null ) => {
46
+ if ( 'kit-library' === referrer ) {
47
+ appsEventTrackingDispatch(
48
+ command,
49
+ {
50
+ page_source: source,
51
+ event_type: eventType,
52
+ element_location: elementLocation,
53
+ },
54
+ );
55
+ }
56
+ },
57
+ kitData = useMemo( () => getKitData(), [] );
58
+
59
+ useEffect( () => {
60
+ if ( ! uploadedData ) {
61
+ navigate( '/import' );
62
+ }
63
+ if ( uploadedData ) {
64
+ eventTracking( 'kit-library/kit-is-live-load', 'kit is live', 'load' );
65
+ }
66
+ sharedContext.dispatch( { type: 'SET_CURRENT_PAGE_NAME', payload: ImportComplete.name } );
67
+ }, [] );
68
+
69
+ return (
70
+ <Layout type="import" footer={ <ImportCompleteFooter seeItLiveUrl={ seeItLiveUrl } referrer={ referrer } /> }>
71
+ <WizardStep
72
+ image={ elementorAppConfig.assets_url + 'images/go-pro.svg' }
73
+ heading={ __( 'Your kit is now live on your site!', 'elementor' ) }
74
+ description={ __( 'You’ve imported and applied the following to your site:', 'elementor' ) }
75
+ notice={ (
76
+ <>
77
+ <InlineLink
78
+ url="https://go.elementor.com/app-what-are-kits"
79
+ italic
80
+ onClick={ () => eventTracking( 'kit-library/seek-more-info', 'kit is live', 'click', 'app_header' ) }
81
+ >
82
+ { __( 'Click here', 'elementor' ) }
83
+ </InlineLink> { __( 'to learn more about building your site with Elementor Kits', 'elementor' ) }
84
+ </>
85
+ ) }
86
+ >
87
+ { ! ! failedPlugins.length && <FailedPluginsNotice failedPlugins={ failedPlugins } /> }
88
+
89
+ { isProInstalledDuringProcess && <ConnectProNotice /> }
90
+
91
+ <KitData data={ kitData } />
92
+ </WizardStep>
93
+ </Layout>
94
+ );
95
+ }
app/modules/import-export/assets/js/pages/import/import-content/components/import-content-display/import-content-display.js ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useContext, useEffect } from 'react';
2
+ import KitContent from '../../../../../shared/kit-content/kit-content';
3
+ import { SharedContext } from '../../../../../context/shared-context/shared-context-provider';
4
+ import kitContentData from '../../../../../shared/kit-content-data/kit-content-data';
5
+ import Notice from 'elementor-app/ui/molecules/notice';
6
+ import InlineLink from 'elementor-app/ui/molecules/inline-link';
7
+ import { cptObjectToOptionsArray } from '../../../../../shared/cpt-select-box/cpt-object-to-options-array';
8
+
9
+ export default function ImportContentDisplay( {
10
+ manifest,
11
+ hasPro,
12
+ hasPlugins,
13
+ isAllRequiredPluginsSelected,
14
+ onResetProcess,
15
+ } ) {
16
+ const sharedContext = useContext( SharedContext );
17
+ // Getting the kit data from the manifest.
18
+ const kitData = kitContentData.filter( ( { type } ) => {
19
+ const contentType = 'settings' === type ? 'site-settings' : type,
20
+ data = manifest?.[ contentType ];
21
+
22
+ return ! ! ( Array.isArray( data ) ? data.length : data );
23
+ } );
24
+
25
+ useEffect( () => {
26
+ sharedContext.dispatch( { type: 'SET_CPT', payload: cptObjectToOptionsArray( manifest?.[ 'custom-post-type-title' ], 'label' ) } );
27
+ }, [] );
28
+
29
+ if ( ! kitData.length && hasPlugins ) {
30
+ return (
31
+ <Notice color="info" label={ __( 'Note:', 'elementor' ) }>
32
+ { __( 'The Website Kit you’re using contains plugins for functionality, but no content or pages, etc.', 'elementor' ) }
33
+ </Notice>
34
+ );
35
+ }
36
+
37
+ if ( ! kitData.length ) {
38
+ return (
39
+ <Notice color="danger">
40
+ { __( 'You can’t use this Website Kit because it doesn’t contain any content, pages, etc. Try again with a different file.', 'elementor' ) } <InlineLink onClick={ onResetProcess }>{ __( 'Go Back', 'elementor' ) }</InlineLink>
41
+ </Notice>
42
+ );
43
+ }
44
+
45
+ return (
46
+ <>
47
+ {
48
+ ! isAllRequiredPluginsSelected &&
49
+ <Notice color="warning" label={ __( 'Required plugins are still missing.', 'elementor' ) } className="e-app-import-content__plugins-notice">
50
+ { __( "If you don't include them, this kit may not work properly.", 'elementor' ) } <InlineLink url="/import/plugins">{ __( 'Go Back', 'elementor' ) }</InlineLink>
51
+ </Notice>
52
+ }
53
+
54
+ <KitContent contentData={ kitData } hasPro={ hasPro } />
55
+ </>
56
+ );
57
+ }
58
+
59
+ ImportContentDisplay.propTypes = {
60
+ manifest: PropTypes.object,
61
+ hasPro: PropTypes.bool,
62
+ hasPlugins: PropTypes.bool,
63
+ isAllRequiredPluginsSelected: PropTypes.bool,
64
+ onResetProcess: PropTypes.func,
65
+ };
app/modules/import-export/assets/js/pages/import/import-content/components/import-content-footer/import-content-footer.js ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useNavigate } from '@reach/router';
2
+
3
+ import ActionsFooter from '../../../../../shared/actions-footer/actions-footer';
4
+ import Button from 'elementor-app/ui/molecules/button';
5
+
6
+ export default function ImportContentFooter( { hasPlugins, hasConflicts, isImportAllowed, onResetProcess, onPreviousClick, onImportClick } ) {
7
+ const navigate = useNavigate(),
8
+ getNextPageUrl = () => {
9
+ if ( hasConflicts ) {
10
+ return 'import/resolver';
11
+ } else if ( hasPlugins ) {
12
+ return 'import/plugins-activation';
13
+ }
14
+
15
+ return 'import/process';
16
+ };
17
+
18
+ return (
19
+ <ActionsFooter>
20
+ <Button
21
+ text={ __( 'Previous', 'elementor' ) }
22
+ variant="contained"
23
+ onClick={ () => {
24
+ onPreviousClick?.();
25
+ if ( hasPlugins ) {
26
+ navigate( 'import/plugins/' );
27
+ } else {
28
+ onResetProcess();
29
+ }
30
+ } }
31
+ />
32
+
33
+ <Button
34
+ variant="contained"
35
+ text={ __( 'Import', 'elementor' ) }
36
+ color={ isImportAllowed ? 'primary' : 'disabled' }
37
+ onClick={ () => {
38
+ onImportClick?.();
39
+ return isImportAllowed && navigate( getNextPageUrl() );
40
+ } }
41
+ />
42
+ </ActionsFooter>
43
+ );
44
+ }
45
+
46
+ ImportContentFooter.propTypes = {
47
+ hasPlugins: PropTypes.bool,
48
+ hasConflicts: PropTypes.bool,
49
+ isImportAllowed: PropTypes.bool,
50
+ onResetProcess: PropTypes.func.isRequired,
51
+ onPreviousClick: PropTypes.func,
52
+ onImportClick: PropTypes.func,
53
+ };
app/modules/import-export/assets/js/pages/import/import-content/import-content.js ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useContext, useEffect } from 'react';
2
+
3
+ import { SharedContext } from '../../../context/shared-context/shared-context-provider';
4
+ import { ImportContext } from '../../../context/import-context/import-context-provider';
5
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
6
+ import Layout from '../../../templates/layout';
7
+ import PageHeader from '../../../ui/page-header/page-header';
8
+ import ImportContentDisplay from './components/import-content-display/import-content-display';
9
+ import ImportContentFooter from './components/import-content-footer/import-content-footer';
10
+
11
+ import useImportActions from '../hooks/use-import-actions';
12
+
13
+ import './import-content.scss';
14
+
15
+ export default function ImportContent() {
16
+ const sharedContext = useContext( SharedContext ),
17
+ importContext = useContext( ImportContext ),
18
+ { referrer, includes, currentPage } = sharedContext.data,
19
+ { plugins, requiredPlugins, uploadedData, file, isProInstalledDuringProcess } = importContext.data,
20
+ { navigateToMainScreen } = useImportActions(),
21
+ handleResetProcess = () => importContext.dispatch( { type: 'SET_FILE', payload: null } ),
22
+ eventTracking = ( command ) => {
23
+ if ( 'kit-library' === referrer ) {
24
+ appsEventTrackingDispatch(
25
+ command,
26
+ {
27
+ page_source: 'import',
28
+ step: currentPage,
29
+ event_type: 'click',
30
+ },
31
+ );
32
+ }
33
+ },
34
+ getFooter = () => {
35
+ return (
36
+ <ImportContentFooter
37
+ hasPlugins={ ! ! plugins.length }
38
+ hasConflicts={ ! ! ( includes.includes( 'templates' ) && uploadedData?.conflicts && Object.keys( uploadedData.conflicts ).length ) }
39
+ isImportAllowed={ ! ! ( plugins.length || includes.length ) }
40
+ onResetProcess={ handleResetProcess }
41
+ onPreviousClick={ () => eventTracking( 'kit-library/go-back' ) }
42
+ onImportClick={ () => eventTracking( 'kit-library/approve-import' ) }
43
+ />
44
+ );
45
+ };
46
+
47
+ useEffect( () => {
48
+ sharedContext.dispatch( { type: 'SET_CURRENT_PAGE_NAME', payload: ImportContent.name } );
49
+ }, [] );
50
+ // On file change.
51
+ useEffect( () => {
52
+ if ( ! file ) {
53
+ navigateToMainScreen();
54
+ }
55
+ }, [ file ] );
56
+
57
+ return (
58
+ <Layout type="import" footer={ getFooter() }>
59
+ <section className="e-app-import-content">
60
+ <PageHeader
61
+ heading={ __( 'Select which parts you want to apply', 'elementor' ) }
62
+ description={ [
63
+ __( 'These are the templates, content and site settings that come with your kit.', 'elementor' ),
64
+ __( "All items are already selected by default. Uncheck the ones you don't want.", 'elementor' ),
65
+ ] }
66
+ />
67
+ <ImportContentDisplay
68
+ manifest={ uploadedData?.manifest }
69
+ hasPro={ isProInstalledDuringProcess }
70
+ hasPlugins={ ! ! requiredPlugins.length }
71
+ isAllRequiredPluginsSelected={ requiredPlugins.length === plugins.length }
72
+ onResetProcess={ handleResetProcess }
73
+ />
74
+ </section>
75
+ </Layout>
76
+ );
77
+ }
app/modules/import-export/assets/js/pages/import/import-content/import-content.scss ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ .e-app-import-content {
2
+ padding-bottom : spacing(20);
3
+
4
+ &__plugins-notice {
5
+ margin-bottom: spacing(20);
6
+ }
7
+ }
app/modules/import-export/assets/js/pages/import/import-kit/hooks/use-import-kit-library-apply-all-plugins.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useState } from 'react';
2
+ import usePlugins from '../../../../hooks/use-plugins';
3
+ import usePluginsData from '../../../../hooks/use-plugins-data';
4
+ import useImportPluginsData from '../../import-plugins/hooks/use-import-plugins-data';
5
+
6
+ export function useImportKitLibraryApplyAllPlugins( plugins ) {
7
+ const [ missingPlugins, setMissingPlugins ] = useState(),
8
+ { response } = usePlugins(),
9
+ { pluginsData } = usePluginsData( response.data ),
10
+ { importPluginsData } = useImportPluginsData( plugins, pluginsData ),
11
+ { missing } = importPluginsData || {};
12
+
13
+ useEffect( () => {
14
+ if ( plugins && ! plugins.length ) {
15
+ return;
16
+ }
17
+ setMissingPlugins( missing );
18
+ }, [ plugins, missing ] );
19
+
20
+ return missingPlugins;
21
+ }
app/modules/import-export/assets/js/pages/import/import-kit/import-kit.js ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useContext } from 'react';
2
+ import { useNavigate } from '@reach/router';
3
+
4
+ import { SharedContext } from '../../../context/shared-context/shared-context-provider';
5
+ import { ImportContext } from '../../../context/import-context/import-context-provider';
6
+
7
+ import Layout from '../../../templates/layout';
8
+ import PageHeader from '../../../ui/page-header/page-header';
9
+ import ProcessFailedDialog from '../../../shared/process-failed-dialog/process-failed-dialog';
10
+ import InlineLink from 'elementor-app/ui/molecules/inline-link';
11
+ import Notice from 'elementor-app/ui/molecules/notice';
12
+ import DropZone from 'elementor-app/organisms/drop-zone';
13
+ import Button from 'elementor-app/ui/molecules/button';
14
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
15
+
16
+ import useKit from '../../../hooks/use-kit';
17
+
18
+ import './import-kit.scss';
19
+
20
+ export default function ImportKit() {
21
+ const sharedContext = useContext( SharedContext ),
22
+ importContext = useContext( ImportContext ),
23
+ navigate = useNavigate(),
24
+ { kitState, kitActions, KIT_STATUS_MAP } = useKit(),
25
+ [ errorType, setErrorType ] = useState( '' ),
26
+ [ isLoading, setIsLoading ] = useState( false ),
27
+ { referrer, currentPage } = sharedContext.data,
28
+ resetImportProcess = () => {
29
+ importContext.dispatch( { type: 'SET_FILE', payload: null } );
30
+ setErrorType( null );
31
+ setIsLoading( false );
32
+ kitActions.reset();
33
+ },
34
+ eventTracking = ( command, event = null, eventType = 'click', error = null, modalType = null, uploadMethod ) => {
35
+ if ( 'kit-library' === referrer ) {
36
+ let uploadMethodName = null;
37
+ if ( uploadMethod ) {
38
+ uploadMethodName = 'drop' === uploadMethod ? 'drag-drop' : 'browse';
39
+ }
40
+
41
+ let element = null;
42
+ if ( event && 'eps-button eps-dialog__button' === event.currentTarget.className.trim() ) {
43
+ element = 'close button';
44
+ } else if ( event && 'eps-button eps-dialog__close-button' === event.currentTarget.className.trim() ) {
45
+ element = 'x';
46
+ }
47
+
48
+ appsEventTrackingDispatch(
49
+ command,
50
+ {
51
+ element,
52
+ page_source: 'import',
53
+ event_type: eventType,
54
+ step: currentPage,
55
+ error: 'general' === error ? 'unknown' : error,
56
+ modal_type: modalType,
57
+ method: uploadMethodName,
58
+ },
59
+ );
60
+ }
61
+ },
62
+ getLearnMoreLink = () => (
63
+ <InlineLink url="https://go.elementor.com/app-what-are-kits" key="learn-more-link" italic onClick={ () => eventTracking( 'kit-library/seek-more-info', null, 'click' ) } >
64
+ { __( 'Learn More', 'elementor' ) }
65
+ </InlineLink>
66
+ );
67
+
68
+ // On load.
69
+ useEffect( () => {
70
+ sharedContext.dispatch( { type: 'SET_INCLUDES', payload: [] } );
71
+ sharedContext.dispatch( { type: 'SET_CURRENT_PAGE_NAME', payload: ImportKit.name } );
72
+ }, [] );
73
+
74
+ // Uploading the kit after file is selected.
75
+ useEffect( () => {
76
+ if ( importContext.data.file ) {
77
+ kitActions.upload( { file: importContext.data.file } );
78
+ }
79
+ }, [ importContext.data.file ] );
80
+
81
+ // Listening to kit upload state.
82
+ useEffect( () => {
83
+ if ( KIT_STATUS_MAP.UPLOADED === kitState.status ) {
84
+ importContext.dispatch( { type: 'SET_UPLOADED_DATA', payload: kitState.data } );
85
+ } else if ( 'error' === kitState.status ) {
86
+ setErrorType( kitState.data );
87
+ }
88
+ }, [ kitState.status ] );
89
+
90
+ // After kit was uploaded.
91
+ useEffect( () => {
92
+ if ( importContext.data.uploadedData && importContext.data.file ) {
93
+ const url = importContext.data.uploadedData.manifest.plugins ? '/import/plugins' : '/import/content';
94
+
95
+ navigate( url );
96
+ }
97
+ }, [ importContext.data.uploadedData ] );
98
+
99
+ return (
100
+ <Layout type="import">
101
+ <section className="e-app-import">
102
+ {
103
+ 'kit-library' === referrer &&
104
+ <Button
105
+ className="e-app-import__back-to-library"
106
+ icon="eicon-chevron-left"
107
+ text={ __( 'Back to Kit Library', 'elementor' ) }
108
+ url="/kit-library"
109
+ />
110
+ }
111
+
112
+ <PageHeader
113
+ heading={ __( 'Import a Website Kit', 'elementor' ) }
114
+ description={ [
115
+ __( 'Upload a file with templates, site settings, content, etc., and apply them to your site automatically.', 'elementor' ),
116
+ getLearnMoreLink(),
117
+ ] }
118
+ />
119
+
120
+ <Notice label={ __( 'Important:', 'elementor' ) } color="warning" className="e-app-import__notice">
121
+ { __( 'We recommend that you backup your site before importing a kit file.', 'elementor' ) }
122
+ </Notice>
123
+
124
+ <DropZone
125
+ className="e-app-import__drop-zone"
126
+ heading={ __( 'Upload Files to Your Library', 'elementor' ) }
127
+ text={ __( 'Drag & drop the .zip file with your Kit', 'elementor' ) }
128
+ secondaryText={ __( 'Or', 'elementor' ) }
129
+ filetypes={ [ 'zip' ] }
130
+ onFileChoose={ () => eventTracking( 'kit-library/choose-file' ) }
131
+ onFileSelect={ ( file, e ) => {
132
+ setIsLoading( true );
133
+ importContext.dispatch( { type: 'SET_FILE', payload: file } );
134
+ eventTracking( 'kit-library/file-upload', null, 'feedback', null, null, e.type );
135
+ } }
136
+ onError={ () => setErrorType( 'general' ) }
137
+ isLoading={ isLoading }
138
+ />
139
+
140
+ { errorType && <ProcessFailedDialog
141
+ errorType={ errorType }
142
+ onApprove={ resetImportProcess }
143
+ onModalClose={ ( event ) => eventTracking( 'kit-library/modal-close', event, 'load', null, 'error' ) }
144
+ onError={ () => eventTracking( 'kit-library/modal-open', null, 'load', errorType, 'error' ) }
145
+ onLearnMore={ () => eventTracking( 'kit-library/seek-more-info', null, 'click', null, 'error' ) }
146
+ /> }
147
+ </section>
148
+ </Layout>
149
+ );
150
+ }
app/modules/import-export/assets/js/pages/import/import-kit/import-kit.scss ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-import-back-to-library-color: tints(500);
2
+ $e-app-import-back-to-library-dark-color: dark-tints(200);
3
+
4
+ $root: e-app-import;
5
+
6
+ .#{$root} {
7
+ --e-app-import-back-to-library-color: #{$e-app-import-back-to-library-color};
8
+
9
+ padding-bottom: spacing(20);
10
+
11
+ &__drop-zone {
12
+ margin-top: spacing(20);
13
+ }
14
+
15
+ &__back-to-library {
16
+ color: var(--e-app-import-back-to-library-color);
17
+ margin-bottom: spacing(24);
18
+
19
+ > i {
20
+ margin-inline-end: spacing(8);
21
+ }
22
+ }
23
+ }
24
+
25
+ .eps-theme-dark {
26
+ .#{$root} {
27
+ --e-app-import-back-to-library-color: #{$e-app-import-back-to-library-dark-color};
28
+ }
29
+ }
app/modules/import-export/assets/js/pages/import/import-plugins-activation/components/plugin-status-item/plugin-status-item.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Grid from 'elementor-app/ui/grid/grid';
2
+ import Checkbox from 'elementor-app/ui/atoms/checkbox';
3
+ import Text from 'elementor-app/ui/atoms/text';
4
+
5
+ import { PLUGIN_STATUS_MAP } from '../../../../../hooks/use-plugins';
6
+
7
+ const { ACTIVE, INACTIVE, NOT_INSTALLED } = PLUGIN_STATUS_MAP;
8
+
9
+ export default function PluginStatusItem( { name, status } ) {
10
+ if ( NOT_INSTALLED === status ) {
11
+ return null;
12
+ } else if ( INACTIVE === status ) {
13
+ status = 'installed';
14
+ } else if ( ACTIVE === status ) {
15
+ status = 'activated';
16
+ }
17
+
18
+ return (
19
+ <Grid container alignItems="center" key={ name }>
20
+ <Checkbox rounded checked error={ 'failed' === status || null } onChange={ () => {} } />
21
+
22
+ <Text tag="span" variant="xs" className="e-app-import-plugins-activation__plugin-name">
23
+ { name + ' ' + status }
24
+ </Text>
25
+ </Grid>
26
+ );
27
+ }
28
+
29
+ PluginStatusItem.propTypes = {
30
+ name: PropTypes.string.isRequired,
31
+ status: PropTypes.string.isRequired,
32
+ };
app/modules/import-export/assets/js/pages/import/import-plugins-activation/hooks/use-install-plugins.js ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useMemo } from 'react';
2
+
3
+ import usePlugins, { PLUGINS_RESPONSE_MAP, PLUGIN_STATUS_MAP } from '../../../../hooks/use-plugins';
4
+
5
+ export const ACTION_STATUS_MAP = Object.freeze( {
6
+ ACTIVATED: 'activated',
7
+ INSTALLED: 'installed',
8
+ FAILED: 'failed',
9
+ } );
10
+
11
+ export default function useInstallPlugins( { plugins = [], bulkMaxItems = 5 } ) {
12
+ const { response, pluginsActions } = usePlugins(),
13
+ [ isPluginsFetched, setIsPluginsFetched ] = useState( false ),
14
+ [ isDone, setIsDone ] = useState( false ),
15
+ [ bulk, setBulk ] = useState( [] ),
16
+ [ ready, setReady ] = useState( [] ),
17
+ [ actionStatus, setActionStatus ] = useState( '' ),
18
+ [ currentPlugin, setCurrentPlugin ] = useState( null ),
19
+ isError = PLUGINS_RESPONSE_MAP.ERROR === response.status,
20
+ getBulk = () => {
21
+ if ( bulk.length > bulkMaxItems ) {
22
+ // Getting a bulk for display, when needed to display only X plugins data that are in process.
23
+ return bulk.slice( bulk.length - bulkMaxItems, bulk.length );
24
+ }
25
+
26
+ return bulk;
27
+ };
28
+
29
+ // Setting the next plugin to activate/install and checking when all plugins ar ready.
30
+ useEffect( () => {
31
+ if ( plugins.length ) {
32
+ if ( ready.length === plugins.length ) {
33
+ setIsDone( true );
34
+ } else if ( isPluginsFetched ) {
35
+ const nextPluginToInstallIndex = ready.length;
36
+
37
+ setCurrentPlugin( plugins[ nextPluginToInstallIndex ] );
38
+ }
39
+ }
40
+ }, [ ready, isPluginsFetched ] );
41
+
42
+ // Activating/installing the current plugin.
43
+ useEffect( () => {
44
+ if ( currentPlugin ) {
45
+ const runAction = PLUGIN_STATUS_MAP.INACTIVE === currentPlugin.status ? pluginsActions.activate : pluginsActions.install;
46
+
47
+ runAction( currentPlugin.plugin );
48
+ }
49
+ }, [ currentPlugin ] );
50
+
51
+ // Status Updater.
52
+ useEffect( () => {
53
+ if ( PLUGINS_RESPONSE_MAP.SUCCESS === response.status ) {
54
+ const { data } = response;
55
+
56
+ if ( Array.isArray( data ) ) {
57
+ // When the data type is an Array it means that the plugins data was fetched.
58
+ setIsPluginsFetched( true );
59
+ } else if ( ! Object.prototype.hasOwnProperty.call( data, 'plugin' ) ) {
60
+ setActionStatus( ACTION_STATUS_MAP.FAILED );
61
+ } else if ( PLUGIN_STATUS_MAP.ACTIVE === data.status ) {
62
+ setActionStatus( ACTION_STATUS_MAP.ACTIVATED );
63
+ } else if ( PLUGIN_STATUS_MAP.INACTIVE === data.status ) {
64
+ setActionStatus( ACTION_STATUS_MAP.INSTALLED );
65
+ }
66
+ } else if ( PLUGINS_RESPONSE_MAP.ERROR === response.status ) {
67
+ setActionStatus( ACTION_STATUS_MAP.FAILED );
68
+ }
69
+ }, [ response.status ] );
70
+
71
+ // Actions after data response.
72
+ useEffect( () => {
73
+ if ( actionStatus ) {
74
+ const pluginData = ACTION_STATUS_MAP.FAILED === actionStatus ? { ...currentPlugin, status: ACTION_STATUS_MAP.FAILED } : response.data;
75
+
76
+ // Updating the current plugin status in the bulk.
77
+ setBulk( ( prevState ) => {
78
+ const processedPlugins = [ ...prevState ];
79
+
80
+ processedPlugins[ ready.length ] = pluginData;
81
+
82
+ return processedPlugins;
83
+ } );
84
+
85
+ if ( ACTION_STATUS_MAP.ACTIVATED === actionStatus || ACTION_STATUS_MAP.FAILED === actionStatus ) {
86
+ // After the plugin process was finished.
87
+ setReady( ( prevState ) => [ ...prevState, pluginData ] );
88
+ } else if ( ACTION_STATUS_MAP.INSTALLED === actionStatus ) {
89
+ // In case that the widget was installed it will be inactive after the installation and therefore should be activated manually.
90
+ setCurrentPlugin( pluginData );
91
+ }
92
+
93
+ // Reset the actionStatus value for the next iteration.
94
+ setActionStatus( '' );
95
+ }
96
+ }, [ actionStatus ] );
97
+
98
+ return {
99
+ isDone,
100
+ ready,
101
+ bulk: useMemo( () => getBulk(), [ bulk ] ),
102
+ isError,
103
+ };
104
+ }
app/modules/import-export/assets/js/pages/import/import-plugins-activation/import-plugins-activation.js ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext, useEffect } from 'react';
2
+ import { useNavigate } from '@reach/router';
3
+
4
+ import { ImportContext } from '../../../context/import-context/import-context-provider';
5
+ import { SharedContext } from '../../../context/shared-context/shared-context-provider';
6
+
7
+ import Layout from '../../../templates/layout';
8
+ import FileProcess from '../../../shared/file-process/file-process';
9
+ import PluginStatusItem from './components/plugin-status-item/plugin-status-item';
10
+
11
+ import Grid from 'elementor-app/ui/grid/grid';
12
+ import List from 'elementor-app/ui/molecules/list';
13
+
14
+ import './import-plugins-activation.scss';
15
+
16
+ import useInstallPlugins from './hooks/use-install-plugins';
17
+
18
+ export default function ImportPluginsActivation() {
19
+ const importContext = useContext( ImportContext ),
20
+ sharedContext = useContext( SharedContext ),
21
+ navigate = useNavigate(),
22
+ { bulk, ready, isDone } = useInstallPlugins( { plugins: importContext.data.plugins } );
23
+
24
+ // In case there are no plugins to import.
25
+ useEffect( () => {
26
+ if ( ! importContext.data.plugins.length ) {
27
+ navigate( '/import/' );
28
+ }
29
+ }, [ importContext.data.plugins ] );
30
+
31
+ // When import plugins process is done.
32
+ useEffect( () => {
33
+ if ( isDone ) {
34
+ importContext.dispatch( { type: 'SET_IMPORTED_PLUGINS', payload: ready } );
35
+ importContext.dispatch( { type: 'SET_PLUGINS_STATE', payload: 'success' } );
36
+ sharedContext.dispatch( { type: 'SET_CURRENT_PAGE_NAME', payload: ImportPluginsActivation.name } );
37
+ }
38
+ }, [ isDone ] );
39
+
40
+ // Once the imported plugins data was updated.
41
+ useEffect( () => {
42
+ if ( importContext.data.importedPlugins.length ) {
43
+ navigate( '/import/process' );
44
+ }
45
+ }, [ importContext.data.importedPlugins ] );
46
+
47
+ return (
48
+ <Layout type="import">
49
+ <section className="e-app-import-plugins-activation">
50
+ <FileProcess info={ __( 'Activating plugins:', 'elementor' ) } />
51
+
52
+ <Grid container justify="center">
53
+ <Grid item className="e-app-import-plugins-activation__installing-plugins">
54
+ {
55
+ ! ! bulk?.length &&
56
+ <List>
57
+ {
58
+ bulk.map( ( plugin ) => (
59
+ <List.Item className="e-app-import-plugins-activation__plugin-status-item" key={ plugin.name }>
60
+ <PluginStatusItem
61
+ name={ plugin.name }
62
+ status={ plugin.status }
63
+ />
64
+ </List.Item>
65
+ ) )
66
+ }
67
+ </List>
68
+ }
69
+ </Grid>
70
+ </Grid>
71
+ </section>
72
+ </Layout>
73
+ );
74
+ }
app/modules/import-export/assets/js/pages/import/import-plugins-activation/import-plugins-activation.scss ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-app-import-plugins-activation {
2
+ &__installing-plugins {
3
+ padding: spacing(10) 0;
4
+ }
5
+
6
+ &__plugin-name {
7
+ margin-inline-start: spacing(8);
8
+ }
9
+
10
+ &__plugin-status-item {
11
+ margin-bottom: spacing(12);
12
+ }
13
+ }
app/modules/import-export/assets/js/pages/import/import-plugins/components/existing-plugins/existing-plugins.js ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMemo } from 'react';
2
+
3
+ import PluginsSelection from '../../../../../shared/plugins-selection/plugins-selection';
4
+ import Heading from 'elementor-app/ui/atoms/heading';
5
+
6
+ const layout = [ 4, 1 ];
7
+
8
+ export default function ExistingPlugins( { plugins } ) {
9
+ if ( ! plugins?.length ) {
10
+ return null;
11
+ }
12
+
13
+ // eslint-disable-next-line react-hooks/rules-of-hooks
14
+ const existingPlugins = useMemo( () => plugins, [] ),
15
+ // eslint-disable-next-line react-hooks/rules-of-hooks
16
+ initialSelected = useMemo( () => plugins.map( ( plugin, index ) => index ), [] );
17
+
18
+ return (
19
+ <div className="e-app-import-plugins__section">
20
+ <Heading variant="h5" tag="h3" className="e-app-import-plugins__section-heading">
21
+ { __( 'Plugins you already have:', 'elementor' ) }
22
+ </Heading>
23
+
24
+ <PluginsSelection
25
+ withHeader={ false }
26
+ withStatus={ false }
27
+ plugins={ existingPlugins }
28
+ initialSelected={ initialSelected }
29
+ initialDisabled={ initialSelected }
30
+ excludeSelections={ initialSelected }
31
+ layout={ layout }
32
+ />
33
+ </div>
34
+ );
35
+ }
36
+
37
+ ExistingPlugins.propTypes = {
38
+ plugins: PropTypes.array,
39
+ };
app/modules/import-export/assets/js/pages/import/import-plugins/components/import-plugins-footer/import-plugins-footer.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+
3
+ import { ImportContext } from '../../../../../context/import-context/import-context-provider';
4
+
5
+ import ActionsFooter from '../../../../../shared/actions-footer/actions-footer';
6
+ import Button from 'elementor-app/ui/molecules/button';
7
+
8
+ import useImportActions from '../../../hooks/use-import-actions';
9
+
10
+ export default function ImportPluginsFooter( props ) {
11
+ const importContext = useContext( ImportContext ),
12
+ { navigateToMainScreen } = useImportActions();
13
+
14
+ return (
15
+ <ActionsFooter>
16
+ <Button
17
+ text={ __( 'Previous', 'elementor' ) }
18
+ variant="contained"
19
+ onClick={ () => {
20
+ importContext.dispatch( { type: 'SET_FILE', payload: null } );
21
+
22
+ props.onPreviousClick?.();
23
+ navigateToMainScreen();
24
+ } }
25
+ />
26
+
27
+ <Button
28
+ variant="contained"
29
+ text={ __( 'Next', 'elementor' ) }
30
+ color="primary"
31
+ url="/import/content"
32
+ onClick={ () => {
33
+ props.onNextClick?.();
34
+ } }
35
+ />
36
+ </ActionsFooter>
37
+ );
38
+ }
39
+
40
+ ImportPluginsFooter.propTypes = {
41
+ onPreviousClick: PropTypes.func,
42
+ onNextClick: PropTypes.func,
43
+ };
app/modules/import-export/assets/js/pages/import/import-plugins/components/plugins-to-import/plugins-to-import.js ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext, useMemo, useCallback } from 'react';
2
+
3
+ import { ImportContext } from '../../../../../context/import-context/import-context-provider';
4
+
5
+ import PluginsSelection from '../../../../../shared/plugins-selection/plugins-selection';
6
+ import Heading from 'elementor-app/ui/atoms/heading';
7
+
8
+ import { PLUGIN_STATUS_MAP } from '../../../../../hooks/use-plugins';
9
+ import { PLUGINS_KEYS } from '../../../../../hooks/use-plugins-data';
10
+
11
+ const layout = [ 3, 1, 1 ];
12
+
13
+ export default function PluginsToImport( { plugins } ) {
14
+ if ( ! plugins?.length ) {
15
+ return null;
16
+ }
17
+
18
+ // eslint-disable-next-line react-hooks/rules-of-hooks
19
+ const importContext = useContext( ImportContext ),
20
+ getPluginsToImport = () => {
21
+ const { name, status } = plugins[ 0 ];
22
+
23
+ // If Elementor Pro is the first plugin and is not inactive, it should not be displayed.
24
+ if ( PLUGINS_KEYS.ELEMENTOR_PRO === name && PLUGIN_STATUS_MAP.INACTIVE !== status ) {
25
+ return plugins.splice( 1 );
26
+ }
27
+
28
+ return plugins;
29
+ },
30
+ // eslint-disable-next-line react-hooks/rules-of-hooks
31
+ handleOnSelect = useCallback( ( selectedPlugins ) => importContext.dispatch( { type: 'SET_PLUGINS', payload: selectedPlugins } ), [] ),
32
+ // eslint-disable-next-line react-hooks/rules-of-hooks
33
+ pluginsToImport = useMemo( () => getPluginsToImport(), [ plugins ] ),
34
+ // eslint-disable-next-line react-hooks/rules-of-hooks
35
+ initialSelected = useMemo( () => pluginsToImport.map( ( plugin, index ) => index ), [ plugins ] ),
36
+ isAllRequiredPluginsSelected = pluginsToImport.length === importContext.data.plugins.length;
37
+
38
+ if ( ! pluginsToImport.length ) {
39
+ return null;
40
+ }
41
+
42
+ return (
43
+ <div className="e-app-import-plugins__section">
44
+ <Heading variant="h5" tag="h3" className="e-app-import-plugins__section-heading">
45
+ {
46
+ isAllRequiredPluginsSelected
47
+ ? __( 'Plugins to add:', 'elementor' )
48
+ : __( 'Missing Required Plugins:', 'elementor' )
49
+ }
50
+ </Heading>
51
+
52
+ <PluginsSelection
53
+ plugins={ pluginsToImport }
54
+ initialSelected={ initialSelected }
55
+ onSelect={ handleOnSelect }
56
+ layout={ layout }
57
+ />
58
+ </div>
59
+ );
60
+ }
61
+
62
+ PluginsToImport.propTypes = {
63
+ plugins: PropTypes.array,
64
+ };
app/modules/import-export/assets/js/pages/import/import-plugins/components/pro-banner/pro-banner.js ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+
3
+ import MessageBanner from '../../../../../ui/message-banner/message-banner';
4
+ import GoProButton from 'elementor-app/molecules/go-pro-button';
5
+ import Dialog from 'elementor-app/ui/dialog/dialog';
6
+
7
+ import './pro-banner.scss';
8
+
9
+ export default function ProBanner( { onRefresh } ) {
10
+ const [ showInfoDialog, setShowInfoDialog ] = useState( false ),
11
+ openGoProExternalPage = () => window.open( 'https://go.elementor.com/go-pro-import-export/', '_blank' ),
12
+ onDialogDismiss = () => setShowInfoDialog( false ),
13
+ onDialogApprove = () => {
14
+ setShowInfoDialog( false );
15
+
16
+ onRefresh();
17
+ },
18
+ handleGoPro = () => {
19
+ setShowInfoDialog( true );
20
+
21
+ openGoProExternalPage();
22
+ };
23
+
24
+ return (
25
+ <>
26
+ <MessageBanner
27
+ heading={ __( 'Install Elementor Pro', 'elementor' ) }
28
+ description={ __( "Without Elementor Pro, importing components like templates, widgets and popups won't work.", 'elementor' ) }
29
+ button={ <GoProButton onClick={ handleGoPro } /> }
30
+ />
31
+
32
+ {
33
+ showInfoDialog &&
34
+ <Dialog
35
+ title={ __( 'Is your Elementor Pro ready?', 'elementor' ) }
36
+ text={ __( 'If you’ve purchased, installed & activated Elementor Pro, we can continue importing all the parts of this site.', 'elementor' ) }
37
+ approveButtonColor="primary"
38
+ approveButtonText={ __( 'Yes', 'elementor' ) }
39
+ approveButtonOnClick={ onDialogApprove }
40
+ dismissButtonText={ __( 'Not yet', 'elementor' ) }
41
+ dismissButtonOnClick={ onDialogDismiss }
42
+ onClose={ onDialogDismiss }
43
+ />
44
+ }
45
+ </>
46
+ );
47
+ }
48
+
49
+ ProBanner.propTypes = {
50
+ status: PropTypes.string,
51
+ onRefresh: PropTypes.func,
52
+ };
53
+
54
+ ProBanner.defaultProps = {
55
+ status: '',
56
+ };
app/modules/import-export/assets/js/pages/import/import-plugins/components/pro-banner/pro-banner.scss ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-import-plugins-pro-banner-heading-color: tints(700);
2
+ $e-app-import-plugins-pro-banner-heading-dark-color: dark-tints(300);
3
+ $e-app-import-plugins-pro-banner-description-color: tints(600);
4
+ $e-app-import-plugins-pro-banner-description-dark-color: dark-tints(200);
5
+
6
+ $root: e-app-import-plugins-pro-banner;
7
+
8
+ .#{$root} {
9
+ --e-app-import-plugins-pro-banner-heading-color: #{$e-app-import-plugins-pro-banner-heading-color};
10
+ --e-app-import-plugins-pro-banner-description-color: #{$e-app-import-plugins-pro-banner-description-color};
11
+
12
+ margin-bottom: spacing(30);
13
+
14
+ &__heading {
15
+ color: var(--e-app-import-plugins-pro-banner-heading-color);
16
+ margin-bottom: spacing(10);
17
+ }
18
+
19
+ &__description {
20
+ color: var(--e-app-import-plugins-pro-banner-description-color);
21
+ margin-bottom :0;
22
+ }
23
+ }
24
+
25
+ .eps-theme-dark {
26
+ .#{$root} {
27
+ --e-app-import-plugins-pro-banner-heading-color: #{$e-app-import-plugins-pro-banner-heading-dark-color};
28
+ --e-app-import-plugins-pro-banner-description-color: #{$e-app-import-plugins-pro-banner-description-dark-color};
29
+ }
30
+ }
app/modules/import-export/assets/js/pages/import/import-plugins/hooks/use-import-plugins-data.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMemo } from 'react';
2
+
3
+ import { arrayToObjectByKey } from 'elementor-app/utils/utils.js';
4
+
5
+ import { PLUGIN_STATUS_MAP } from '../../../../hooks/use-plugins';
6
+
7
+ const MISSING_PLUGINS_KEY = 'missing',
8
+ EXISTING_PLUGINS_KEY = 'existing',
9
+ ELEMENTOR_PRO_PLUGIN_KEY = 'Elementor Pro';
10
+
11
+ export default function useImportPluginsData( pluginsToInstall, existingPlugins ) {
12
+ const getIsMinVersionExist = ( installedPluginVersion, kitPluginVersion ) => installedPluginVersion.localeCompare( kitPluginVersion ) > -1,
13
+ getClassifiedPlugins = () => {
14
+ const data = {
15
+ missing: [],
16
+ existing: [],
17
+ minVersionMissing: [],
18
+ proData: null,
19
+ },
20
+ installedPluginsMap = arrayToObjectByKey( existingPlugins, 'name' );
21
+
22
+ pluginsToInstall.forEach( ( plugin ) => {
23
+ const installedPluginData = installedPluginsMap[ plugin.name ],
24
+ group = PLUGIN_STATUS_MAP.ACTIVE === installedPluginData?.status ? EXISTING_PLUGINS_KEY : MISSING_PLUGINS_KEY,
25
+ pluginData = installedPluginData || { ...plugin, status: PLUGIN_STATUS_MAP.NOT_INSTALLED };
26
+
27
+ // Verifying that the current installed plugin version is not older than the kit plugin version.
28
+ if ( installedPluginData && ! getIsMinVersionExist( installedPluginData.version, plugin.version ) ) {
29
+ data.minVersionMissing.push( plugin );
30
+ }
31
+
32
+ // In case that the Pro plugin exist saving the data separately for easily knowing if the pro exist or not.
33
+ if ( ELEMENTOR_PRO_PLUGIN_KEY === pluginData.name ) {
34
+ data.proData = pluginData;
35
+ }
36
+
37
+ data[ group ].push( pluginData );
38
+ } );
39
+
40
+ return data;
41
+ },
42
+ classifiedPlugins = useMemo( () => getClassifiedPlugins(), [ pluginsToInstall, existingPlugins ] );
43
+
44
+ return {
45
+ importPluginsData: pluginsToInstall.length && existingPlugins.length ? classifiedPlugins : null,
46
+ };
47
+ }
app/modules/import-export/assets/js/pages/import/import-plugins/import-plugins.js ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect, useContext } from 'react';
2
+ import { useNavigate } from '@reach/router';
3
+
4
+ import { ImportContext } from '../../../context/import-context/import-context-provider';
5
+ import { SharedContext } from '../../../context/shared-context/shared-context-provider';
6
+
7
+ import Layout from '../../../templates/layout';
8
+ import PageHeader from '../../../ui/page-header/page-header';
9
+
10
+ import PluginsToImport from './components/plugins-to-import/plugins-to-import';
11
+ import ExistingPlugins from './components/existing-plugins/existing-plugins';
12
+ import ProBanner from './components/pro-banner/pro-banner';
13
+ import ImportPluginsFooter from './components/import-plugins-footer/import-plugins-footer';
14
+ import Loader from '../../../ui/loader/loader';
15
+
16
+ import Notice from 'elementor-app/ui/molecules/notice';
17
+ import InlineLink from 'elementor-app/ui/molecules/inline-link';
18
+
19
+ import usePlugins, { PLUGIN_STATUS_MAP } from '../../../hooks/use-plugins';
20
+ import usePluginsData from '../../../hooks/use-plugins-data';
21
+ import useImportPluginsData from './hooks/use-import-plugins-data';
22
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
23
+
24
+ import './import-plugins.scss';
25
+
26
+ export default function ImportPlugins() {
27
+ const importContext = useContext( ImportContext ),
28
+ sharedContext = useContext( SharedContext ),
29
+ navigate = useNavigate(),
30
+ kitPlugins = importContext.data.uploadedData?.manifest?.plugins || [],
31
+ { response, pluginsActions } = usePlugins(),
32
+ { pluginsData } = usePluginsData( response.data ),
33
+ { importPluginsData } = useImportPluginsData( kitPlugins, pluginsData ),
34
+ { missing, existing, minVersionMissing, proData } = importPluginsData || {},
35
+ { referrer, currentPage } = sharedContext.data || {},
36
+ handleRequiredPlugins = () => {
37
+ if ( missing.length ) {
38
+ // Saving globally the plugins data that the kit requires in order to work properly.
39
+ importContext.dispatch( { type: 'SET_REQUIRED_PLUGINS', payload: missing } );
40
+ }
41
+ },
42
+ handleRefresh = () => {
43
+ importContext.dispatch( { type: 'SET_REQUIRED_PLUGINS', payload: [] } );
44
+
45
+ pluginsActions.fetch();
46
+ },
47
+ handleProInstallationStatus = () => {
48
+ // In case that the Pro data is now exist but initially in the elementorAppConfig the value was false, it means that the pro was added during the process.
49
+ if ( proData && ! elementorAppConfig.hasPro ) {
50
+ importContext.dispatch( { type: 'SET_IS_PRO_INSTALLED_DURING_PROCESS', payload: true } );
51
+ }
52
+ },
53
+ eventTracking = ( command ) => {
54
+ if ( 'kit-library' === referrer ) {
55
+ appsEventTrackingDispatch(
56
+ command,
57
+ {
58
+ page_source: 'import',
59
+ step: currentPage,
60
+ event_type: 'click',
61
+ },
62
+ );
63
+ }
64
+ };
65
+
66
+ // On load.
67
+ useEffect( () => {
68
+ if ( ! kitPlugins.length ) {
69
+ navigate( 'import/content' );
70
+ }
71
+ sharedContext.dispatch( { type: 'SET_CURRENT_PAGE_NAME', payload: ImportPlugins.name } );
72
+ }, [] );
73
+
74
+ // On plugins data ready.
75
+ useEffect( () => {
76
+ if ( importPluginsData && ! importContext.data.requiredPlugins.length ) {
77
+ // Saving the required plugins to display them on the next screens.
78
+ handleRequiredPlugins();
79
+
80
+ // In case that the pro was installed in the middle of the process, the global state should be updated with the current status.
81
+ handleProInstallationStatus();
82
+ }
83
+ }, [ importPluginsData ] );
84
+
85
+ return (
86
+ <Layout type="import" footer={ <ImportPluginsFooter
87
+ onPreviousClick={ () => eventTracking( 'kit-library/go-back' ) }
88
+ onNextClick={ () => eventTracking( 'kit-library/approve-selection' ) }
89
+ /> } >
90
+ <section className="e-app-import-plugins">
91
+ { ! importPluginsData && <Loader absoluteCenter /> }
92
+
93
+ <PageHeader
94
+ heading={ __( 'Select the plugins you want to import', 'elementor' ) }
95
+ description={ __( 'These are the plugins that powers up your kit. You can deselect them, but it can impact the functionality of your site.', 'elementor' ) }
96
+ />
97
+
98
+ {
99
+ ! ! minVersionMissing?.length &&
100
+ <Notice label={ __( ' Recommended:', 'elementor' ) } className="e-app-import-plugins__versions-notice" color="warning">
101
+ { __( 'Head over to Updates and make sure that your plugins are updated to the latest version.', 'elementor' ) } <InlineLink url={ elementorAppConfig.admin_url + 'update-core.php' }>{ __( 'Take me there', 'elementor' ) }</InlineLink>
102
+ </Notice>
103
+ }
104
+
105
+ { PLUGIN_STATUS_MAP.NOT_INSTALLED === proData?.status && <ProBanner onRefresh={ handleRefresh } /> }
106
+
107
+ <PluginsToImport plugins={ missing } />
108
+
109
+ <ExistingPlugins plugins={ existing } />
110
+ </section>
111
+ </Layout>
112
+ );
113
+ }
app/modules/import-export/assets/js/pages/import/import-plugins/import-plugins.scss ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-import-plugins-selection-section-heading-color: tints(600);
2
+ $e-app-import-plugins-selection-section-heading-dark-color: dark-tints(200);
3
+
4
+ $root: e-app-import-plugins;
5
+
6
+ .#{$root} {
7
+ --e-app-import-plugins-selection-section-heading-color: #{$e-app-import-plugins-selection-section-heading-color};
8
+
9
+ padding-bottom: spacing(24);
10
+
11
+ &__section {
12
+ margin-top: spacing(30);
13
+
14
+ &-heading {
15
+ color: var(--e-app-import-plugins-selection-section-heading-color);
16
+ margin-bottom: spacing(16);
17
+ }
18
+ }
19
+
20
+ &__versions-notice {
21
+ margin-bottom: spacing(12);
22
+ }
23
+ }
24
+
25
+ .eps-theme-dark {
26
+ .#{$root} {
27
+ --e-app-import-plugins-selection-section-heading-color: #{$e-app-import-plugins-selection-section-heading-dark-color};
28
+ }
29
+ }
app/modules/import-export/assets/js/pages/import/import-process/import-process.js ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useContext, useState, useMemo } from 'react';
2
+ import { useNavigate } from '@reach/router';
3
+
4
+ import { SharedContext } from '../../../context/shared-context/shared-context-provider';
5
+ import { ImportContext } from '../../../context/import-context/import-context-provider';
6
+
7
+ import Layout from '../../../templates/layout';
8
+ import FileProcess from '../../../shared/file-process/file-process';
9
+ import UnfilteredFilesDialog from 'elementor-app/organisms/unfiltered-files-dialog';
10
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
11
+
12
+ import useQueryParams from 'elementor-app/hooks/use-query-params';
13
+ import useKit from '../../../hooks/use-kit';
14
+ import useImportActions from '../hooks/use-import-actions';
15
+ import { useImportKitLibraryApplyAllPlugins } from '../import-kit/hooks/use-import-kit-library-apply-all-plugins';
16
+
17
+ export default function ImportProcess() {
18
+ const sharedContext = useContext( SharedContext ),
19
+ importContext = useContext( ImportContext ),
20
+ navigate = useNavigate(),
21
+ [ errorType, setErrorType ] = useState( '' ),
22
+ [ showUnfilteredFilesDialog, setShowUnfilteredFilesDialog ] = useState( false ),
23
+ [ startImport, setStartImport ] = useState( false ),
24
+ [ plugins, setPlugins ] = useState( [] ),
25
+ missing = useImportKitLibraryApplyAllPlugins( plugins ),
26
+ { kitState, kitActions, KIT_STATUS_MAP } = useKit(),
27
+ { referrer, file_url: fileURL, action_type: actionType, nonce } = useQueryParams().getAll(),
28
+ { includes, selectedCustomPostTypes, currentPage } = sharedContext.data || {},
29
+ { file, uploadedData, importedData, overrideConditions, isResolvedData } = importContext.data || {},
30
+ isKitHasSvgAssets = useMemo( () => includes.some( ( item ) => [ 'templates', 'content' ].includes( item ) ), [ includes ] ),
31
+ { navigateToMainScreen } = useImportActions(),
32
+ uploadKit = () => {
33
+ const decodedFileURL = decodeURIComponent( fileURL );
34
+
35
+ importContext.dispatch( { type: 'SET_FILE', payload: decodedFileURL } );
36
+
37
+ kitActions.upload( { file: decodedFileURL, kitLibraryNonce: nonce } );
38
+ },
39
+ importKit = () => {
40
+ if ( elementorAppConfig[ 'import-export' ].isUnfilteredFilesEnabled || ! isKitHasSvgAssets ) {
41
+ setStartImport( true );
42
+ } else {
43
+ setShowUnfilteredFilesDialog( true );
44
+ }
45
+ },
46
+ applyAllSetCpt = () => {
47
+ const cpt = kitState.data?.manifest[ 'custom-post-type-title' ] || importContext.data?.uploadedData?.manifest[ 'custom-post-type-title' ];
48
+ if ( cpt ) {
49
+ const cptArray = Object.keys( cpt );
50
+ sharedContext.dispatch( { type: 'SET_SELECTED_CPT', payload: cptArray } );
51
+ }
52
+ },
53
+ applyAllImportPlugins = () => {
54
+ const allPlugins = ( kitState.data?.manifest?.plugins || importContext.data.uploadedData.manifest.plugins );
55
+ setPlugins( allPlugins );
56
+ },
57
+ onCancelProcess = () => {
58
+ importContext.dispatch( { type: 'SET_FILE', payload: null } );
59
+
60
+ navigateToMainScreen();
61
+ },
62
+ onReady = () => {
63
+ setShowUnfilteredFilesDialog( false );
64
+ setStartImport( true );
65
+ },
66
+ eventTracking = ( command, eventType = 'click' ) => {
67
+ if ( 'kit-library' === sharedContext.data.referrer ) {
68
+ appsEventTrackingDispatch(
69
+ command,
70
+ {
71
+ page_source: 'import',
72
+ step: currentPage,
73
+ modal_type: 'unfiltered_file',
74
+ event_type: eventType,
75
+ },
76
+ );
77
+ }
78
+ };
79
+
80
+ // On load.
81
+ useEffect( () => {
82
+ // Saving the referrer value globally.
83
+ if ( referrer ) {
84
+ sharedContext.dispatch( { type: 'SET_REFERRER', payload: referrer } );
85
+ }
86
+
87
+ if ( actionType ) {
88
+ importContext.dispatch( { type: 'SET_ACTION_TYPE', payload: actionType } );
89
+ }
90
+
91
+ if ( fileURL && ! file ) {
92
+ // When the starting point of the app is the import/process screen and importing via file_url.
93
+ uploadKit();
94
+ } else if ( uploadedData ) {
95
+ // When the import/process is the second step of the kit import process, after selecting the kit content.
96
+ importKit();
97
+ } else {
98
+ navigate( 'import' );
99
+ }
100
+ sharedContext.dispatch( { type: 'SET_CURRENT_PAGE_NAME', payload: ImportProcess.name } );
101
+ }, [] );
102
+
103
+ // Starting the import process.
104
+ useEffect( () => {
105
+ if ( startImport ) {
106
+ kitActions.import( {
107
+ session: uploadedData.session,
108
+ include: includes,
109
+ overrideConditions,
110
+ referrer,
111
+ selectedCustomPostTypes,
112
+ } );
113
+ }
114
+ }, [ startImport ] );
115
+
116
+ // Updating the kit data after upload/import.
117
+ useEffect( () => {
118
+ if ( KIT_STATUS_MAP.INITIAL !== kitState.status ) {
119
+ switch ( kitState.status ) {
120
+ case KIT_STATUS_MAP.IMPORTED:
121
+ importContext.dispatch( { type: 'SET_IMPORTED_DATA', payload: kitState.data } );
122
+ break;
123
+ case KIT_STATUS_MAP.UPLOADED:
124
+ importContext.dispatch( { type: 'SET_UPLOADED_DATA', payload: kitState.data } );
125
+ break;
126
+ case KIT_STATUS_MAP.ERROR:
127
+ setErrorType( kitState.data );
128
+ break;
129
+ }
130
+ }
131
+ }, [ kitState.status ] );
132
+
133
+ // Actions after the kit upload/import data was updated.
134
+ useEffect( () => {
135
+ if ( KIT_STATUS_MAP.INITIAL !== kitState.status || ( isResolvedData && 'apply-all' === importContext.data.actionType ) ) {
136
+ if ( importedData ) { // After kit upload.
137
+ navigate( '/import/complete' );
138
+ } else if ( 'apply-all' === importContext.data.actionType ) { // Forcing apply-all kit content.
139
+ if ( kitState.data?.manifest?.plugins || importContext.data.uploadedData?.manifest.plugins ) {
140
+ importContext.dispatch( { type: 'SET_PLUGINS_STATE', payload: 'have' } );
141
+ }
142
+ if ( uploadedData.conflicts && Object.keys( uploadedData.conflicts ).length && ! isResolvedData ) {
143
+ navigate( '/import/resolver' );
144
+ } else {
145
+ // The kitState must be reset due to staying in the same page, so that the useEffect will be re-triggered.
146
+ kitActions.reset();
147
+
148
+ if ( 'have' === importContext.data.pluginsState ) {
149
+ applyAllImportPlugins();
150
+ }
151
+
152
+ if ( '' === importContext.data.pluginsState || 'success' === importContext.data.pluginsState ) {
153
+ applyAllSetCpt();
154
+ importKit();
155
+ }
156
+ }
157
+ } else {
158
+ navigate( '/import/plugins' );
159
+ }
160
+ }
161
+ }, [ uploadedData, importedData, importContext.data.pluginsState ] );
162
+
163
+ useEffect( () => {
164
+ if ( missing?.length > 0 ) {
165
+ importContext.dispatch( { type: 'SET_PLUGINS', payload: missing } );
166
+ navigate( 'import/plugins-activation' );
167
+ }
168
+ }, [ missing ] );
169
+
170
+ return (
171
+ <Layout type="import">
172
+ <section>
173
+
174
+ <FileProcess
175
+ info={ uploadedData && __( 'Importing your content, templates and site settings', 'elementor' ) }
176
+ errorType={ errorType }
177
+ onDialogApprove={ onCancelProcess }
178
+ onDialogDismiss={ onCancelProcess }
179
+ />
180
+
181
+ <UnfilteredFilesDialog
182
+ show={ showUnfilteredFilesDialog }
183
+ setShow={ setShowUnfilteredFilesDialog }
184
+ confirmModalText={ __( 'This allows Elementor to scan your SVGs for malicious content. Otherwise, you can skip any SVGs in this import.', 'elementor' ) }
185
+ errorModalText={ __( 'Nothing to worry about, just continue without importing SVGs or go back and start the import again.', 'elementor' ) }
186
+ onReady={ () => onReady() }
187
+ onCancel={ () => {
188
+ setShowUnfilteredFilesDialog( false );
189
+ onCancelProcess();
190
+ } }
191
+ onLoad={ () => eventTracking( 'kit-library/modal-load', 'load' ) }
192
+ onClose={ () => {
193
+ eventTracking( 'kit-library/close' );
194
+ onReady();
195
+ } }
196
+ onDismiss={ () => {
197
+ onReady();
198
+ eventTracking( 'kit-library/skip' );
199
+ } }
200
+ onEnable={ () => eventTracking( 'kit-library/enable' ) }
201
+ />
202
+ </section>
203
+ </Layout>
204
+ );
205
+ }
app/modules/import-export/assets/js/pages/import/import-resolver/components/conflict/components/conflict-checkbox/conflict-checkbox.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext, useEffect } from 'react';
2
+
3
+ import { ImportContext } from '../../../../../../../context/import-context/import-context-provider';
4
+
5
+ import Checkbox from 'elementor-app/ui/atoms/checkbox';
6
+
7
+ export default function ConflictCheckbox( props ) {
8
+ const importContext = useContext( ImportContext ),
9
+ isSelected = () => importContext.data.overrideConditions.includes( props.id ),
10
+ updateOverrideCondition = ( event ) => {
11
+ const isChecked = event.target.checked,
12
+ actionType = isChecked ? 'ADD_OVERRIDE_CONDITION' : 'REMOVE_OVERRIDE_CONDITION';
13
+ if ( props.onCheck ) {
14
+ props.onCheck( isChecked );
15
+ }
16
+ importContext.dispatch( { type: actionType, payload: props.id } );
17
+ };
18
+
19
+ useEffect( () => {
20
+ if ( ! importContext.data.overrideConditions.length ) {
21
+ importContext.dispatch( { type: 'ADD_OVERRIDE_CONDITION', payload: props.id } );
22
+ }
23
+ }, [] );
24
+
25
+ return (
26
+ <Checkbox
27
+ checked={ isSelected() }
28
+ onChange={ updateOverrideCondition }
29
+ className={ props.className }
30
+ />
31
+ );
32
+ }
33
+
34
+ ConflictCheckbox.propTypes = {
35
+ className: PropTypes.string,
36
+ id: PropTypes.number.isRequired,
37
+ onCheck: PropTypes.func,
38
+ };
39
+
40
+ ConflictCheckbox.defaultProps = {
41
+ className: '',
42
+ };
app/modules/import-export/assets/js/pages/import/import-resolver/components/conflict/conflict.js ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+
3
+ import { ImportContext } from '../../../../../context/import-context/import-context-provider';
4
+ import { SharedContext } from '../../../../../context/shared-context/shared-context-provider';
5
+
6
+ import ConflictCheckbox from './components/conflict-checkbox/conflict-checkbox';
7
+ import Heading from 'elementor-app/ui/atoms/heading';
8
+ import Text from 'elementor-app/ui/atoms/text';
9
+ import Grid from 'elementor-app/ui/grid/grid';
10
+ import Button from 'elementor-app/ui/molecules/button';
11
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
12
+
13
+ export default function Conflict( props ) {
14
+ const importContext = useContext( ImportContext ),
15
+ sharedContext = useContext( SharedContext ),
16
+ manifest = importContext.data.uploadedData?.manifest,
17
+ { currentPage } = sharedContext.data,
18
+ getConflictTitle = ( id ) => {
19
+ const templateType = manifest.templates[ id ].doc_type,
20
+ summaryTitle = elementorAppConfig[ 'import-export' ].summaryTitles.templates?.[ templateType ];
21
+
22
+ return summaryTitle?.single || templateType;
23
+ },
24
+ getEditTemplateButton = ( editUrl, title ) => (
25
+ <Button
26
+ className="e-app-import-resolver-conflicts__edit-template"
27
+ url={ editUrl }
28
+ target="_blank"
29
+ icon="eicon-editor-external-link"
30
+ text={ __( 'Edit Template', 'elementor' ) }
31
+ hideText
32
+ onClick={ () => {
33
+ if ( props.onClick ) {
34
+ props.onClick( title );
35
+ }
36
+ } }
37
+ />
38
+ ),
39
+ isImportedAssetSelected = ( importedAssetId ) => importContext.data.overrideConditions.includes( importedAssetId ),
40
+ getAssetClassName = ( isActive ) => {
41
+ const classes = [ 'e-app-import-resolver-conflicts__asset' ];
42
+
43
+ if ( isActive ) {
44
+ classes.push( 'active' );
45
+ }
46
+
47
+ return classes.join( ' ' );
48
+ },
49
+ getImportedAssetClasses = ( importedAssetId ) => getAssetClassName( isImportedAssetSelected( importedAssetId ) ),
50
+ getExistingAssetClasses = ( importedAssetId ) => getAssetClassName( ! isImportedAssetSelected( importedAssetId ) ),
51
+ eventTracking = ( command, title ) => appsEventTrackingDispatch(
52
+ `kit-library/${ command }`,
53
+ {
54
+ item: title,
55
+ page_source: 'import',
56
+ step: currentPage,
57
+ event_type: 'click',
58
+ },
59
+ );
60
+ return (
61
+ <Grid container noWrap>
62
+ <ConflictCheckbox
63
+ id={ props.importedId }
64
+ type="main-type"
65
+ className="e-app-import-resolver-conflicts__checkbox"
66
+ onCheck={ ( isChecked ) => {
67
+ const command = isChecked && isChecked ? 'check' : 'uncheck';
68
+ eventTracking( command, props.conflictData.template_title );
69
+ } }
70
+ />
71
+
72
+ <Grid item>
73
+ <Heading variant="h5" tag="h4" className="e-app-import-resolver-conflicts__title">
74
+ { getConflictTitle( props.importedId ) }
75
+ </Heading>
76
+
77
+ <Grid item>
78
+ <Text variant="sm" tag="span" className={ getImportedAssetClasses( props.importedId ) }>
79
+ { __( 'Imported', 'elementor' ) }: { manifest.templates[ props.importedId ].title }
80
+ </Text>
81
+
82
+ <Text style variant="sm" tag="span" className={ getExistingAssetClasses( props.importedId ) }>
83
+ { __( 'Existing', 'elementor' ) }: { props.conflictData.template_title } { getEditTemplateButton( props.conflictData.edit_url, props.conflictData.template_title ) }
84
+ </Text>
85
+ </Grid>
86
+ </Grid>
87
+ </Grid>
88
+ );
89
+ }
90
+
91
+ Conflict.propTypes = {
92
+ importedId: PropTypes.number,
93
+ conflictData: PropTypes.object,
94
+ onClick: PropTypes.func,
95
+ };
app/modules/import-export/assets/js/pages/import/import-resolver/import-resolver.js ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useContext, useEffect } from 'react';
2
+ import { useNavigate } from '@reach/router';
3
+
4
+ import { SharedContext } from '../../../context/shared-context/shared-context-provider';
5
+ import { ImportContext } from '../../../context/import-context/import-context-provider';
6
+
7
+ import Layout from '../../../templates/layout';
8
+ import PageHeader from '../../../ui/page-header/page-header';
9
+ import Conflict from './components/conflict/conflict';
10
+ import ActionsFooter from '../../../shared/actions-footer/actions-footer';
11
+ import Panel from 'elementor-app/ui/panel/panel';
12
+ import Notice from 'elementor-app/ui/molecules/notice';
13
+ import InlineLink from 'elementor-app/ui/molecules/inline-link';
14
+ import Button from 'elementor-app/ui/molecules/button';
15
+ import Box from 'elementor-app/ui/atoms/box';
16
+ import List from 'elementor-app/ui/molecules/list';
17
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
18
+
19
+ import './import-resolver.scss';
20
+
21
+ export default function ImportResolver() {
22
+ const sharedContext = useContext( SharedContext ),
23
+ importContext = useContext( ImportContext ),
24
+ navigate = useNavigate(),
25
+ conflicts = importContext.data?.uploadedData?.conflicts || {},
26
+ { referrer, currentPage } = sharedContext.data || {},
27
+ eventTracking = ( command, sitePart = null ) => {
28
+ if ( 'kit-library' === referrer ) {
29
+ appsEventTrackingDispatch(
30
+ command,
31
+ {
32
+ site_part: sitePart,
33
+ page_source: 'import',
34
+ step: currentPage,
35
+ event_type: 'click',
36
+ },
37
+ );
38
+ }
39
+ },
40
+ getFooter = () => (
41
+ <ActionsFooter>
42
+ <Button
43
+ text={ __( 'Previous', 'elementor' ) }
44
+ variant="contained"
45
+ onClick={ () => {
46
+ eventTracking( 'kit-library/go-back' );
47
+ navigate( 'import/content' );
48
+ } }
49
+ />
50
+
51
+ <Button
52
+ text={ __( 'Next', 'elementor' ) }
53
+ variant="contained"
54
+ color="primary"
55
+ onClick={ () => {
56
+ eventTracking( 'kit-library/approve-selection' );
57
+ const url = importContext.data.plugins.length ? 'import/plugins-activation' : 'import/process';
58
+ importContext.dispatch( { type: 'SET_IS_RESOLVED', payload: true } );
59
+ navigate( url );
60
+ } }
61
+ />
62
+ </ActionsFooter>
63
+ ),
64
+ getLearnMoreLink = () => (
65
+ <InlineLink url="https://go.elementor.com/app-what-are-kits" italic onClick={ () => eventTracking( 'kit-library/seek-more-info' ) }>
66
+ { __( 'Learn More', 'elementor' ) }
67
+ </InlineLink>
68
+ ),
69
+ isHomePageOverride = () => {
70
+ if ( sharedContext.data.includes.includes( 'content' ) ) {
71
+ const pages = importContext.data?.uploadedData?.manifest.content?.page || {};
72
+
73
+ return Object.entries( pages ).find( ( pageData ) => pageData[ 1 ].show_on_front );
74
+ }
75
+
76
+ return false;
77
+ };
78
+
79
+ useEffect( () => {
80
+ if ( ! importContext.data.uploadedData ) {
81
+ navigate( 'import' );
82
+ }
83
+ sharedContext.dispatch( { type: 'SET_CURRENT_PAGE_NAME', payload: ImportResolver.name } );
84
+ }, [] );
85
+
86
+ return (
87
+ <Layout type="import" footer={ getFooter() }>
88
+ <section className="e-app-import-resolver">
89
+ <PageHeader
90
+ heading={ __( 'Import a Website Kit to your site', 'elementor' ) }
91
+ description={ [
92
+ <React.Fragment key="description-first-line">
93
+ { __( 'Parts of this kit overlap with your site’s templates, design and settings. The items you leave checked on this list will replace your current design.', 'elementor' ) } { getLearnMoreLink() }
94
+ </React.Fragment>,
95
+ ] }
96
+ />
97
+
98
+ {
99
+ isHomePageOverride() &&
100
+ <Notice className="e-app-import-resolver__notice" label={ __( 'Note:', 'elementor' ) } color="warning">
101
+ { __( "Your site's homepage will be determined by the kit. You can change this later.", 'elementor' ) }
102
+ </Notice>
103
+ }
104
+
105
+ <Panel isOpened={ true }>
106
+ <Panel.Header toggle={ false }>
107
+ <Panel.Headline>{ __( 'Select the items you want to keep and apply:', 'elementor' ) }</Panel.Headline>
108
+ </Panel.Header>
109
+
110
+ <Panel.Body padding="20">
111
+ <Box className="e-app-import-resolver-conflicts__container">
112
+ <List separated className="e-app-import-resolver-conflicts">
113
+ { Object.entries( conflicts ).map( ( [ id, conflict ], index ) => (
114
+ <List.Item padding="20" key={ index } className="e-app-import-resolver-conflicts__item">
115
+ <Conflict
116
+ importedId={ parseInt( id ) }
117
+ conflictData={ conflict[ 0 ] }
118
+ onClick={ ( title ) => eventTracking( 'kit-library/check-item', title ) }
119
+ />
120
+ </List.Item>
121
+ ) ) }
122
+ </List>
123
+ </Box>
124
+ </Panel.Body>
125
+ </Panel>
126
+ </section>
127
+ </Layout>
128
+ );
129
+ }
app/modules/import-export/assets/js/pages/import/import-resolver/import-resolver.scss ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-import-resolver-panel-header-background-color: theme-colors(light);
2
+ $e-app-import-resolver-panel-header-dark-background-color: dark-tints(500);
3
+ $e-app-import-resolver-panel-body-background-color: rgba(theme-colors(light), $opacity-05);
4
+ $e-app-import-resolver-panel-body-dark-background-color: rgba(dark-theme-colors(dark), $opacity-005);
5
+ $e-app-import-resolver-conflicts-asset-border-color: tints(400);
6
+ $e-app-import-resolver-conflicts-asset-dark-border-color: dark-tints(400);
7
+ $e-app-import-resolver-conflicts-asset-inactive-color: tints(500);
8
+ $e-app-import-resolver-conflicts-asset-inactive-dark-color: dark-tints(300);
9
+
10
+ $root: e-app-import-resolver;
11
+
12
+ .#{$root} {
13
+ --e-app-import-resolver-panel-header-background-color: #{$e-app-import-resolver-panel-header-background-color};
14
+ --e-app-import-resolver-panel-body-background-color: #{$e-app-import-resolver-panel-body-background-color};
15
+ --e-app-import-resolver-conflicts-asset-border-color: #{$e-app-import-resolver-conflicts-asset-border-color};
16
+ --e-app-import-resolver-conflicts-asset-inactive-color: #{$e-app-import-resolver-conflicts-asset-inactive-color};
17
+
18
+ padding-bottom: spacing(20);
19
+
20
+ &__notice {
21
+ margin-bottom: spacing(20);
22
+ }
23
+
24
+ &__panel {
25
+ &,
26
+ &:hover {
27
+ background-color: initial;
28
+ }
29
+
30
+ &-header {
31
+ background-color: var(--e-app-import-resolver-panel-header-background-color);
32
+ }
33
+
34
+ &-body {
35
+ background-color: var(--e-app-import-resolver-panel-body-background-color);
36
+ }
37
+ }
38
+
39
+ &-conflicts {
40
+ &__container {
41
+ box-shadow: $eps-box-shadow-1;
42
+ }
43
+
44
+ &__checkbox {
45
+ flex-shrink: 0;
46
+ margin-inline-end: spacing(12);
47
+ }
48
+
49
+ &__title {
50
+ line-height: 1;
51
+ }
52
+
53
+ &__asset {
54
+ &:not(:first-child) {
55
+ border-inline-start: 2px solid var(--e-app-import-resolver-conflicts-asset-border-color);
56
+ padding-inline-start: spacing(16);
57
+ margin-inline-start: spacing(16);
58
+ }
59
+
60
+ &:not(.active) {
61
+ color: var(--e-app-import-resolver-conflicts-asset-inactive-color);
62
+ }
63
+ }
64
+
65
+ &__edit-template {
66
+ margin-inline-start: spacing(5);
67
+ }
68
+ }
69
+ }
70
+
71
+ .eps-theme-dark {
72
+ .#{$root} {
73
+ --e-app-import-resolver-panel-header-background-color: #{$e-app-import-resolver-panel-header-dark-background-color};
74
+ --e-app-import-resolver-panel-body-background-color: #{$e-app-import-resolver-panel-body-dark-background-color};
75
+ --e-app-import-resolver-conflicts-asset-border-color: #{$e-app-import-resolver-conflicts-asset-dark-border-color};
76
+ --e-app-import-resolver-conflicts-asset-inactive-color: #{$e-app-import-resolver-conflicts-asset-inactive-dark-color};
77
+ }
78
+ }
app/modules/import-export/assets/js/shared/actions-footer/actions-footer.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import WizardFooter from 'elementor-app/organisms/wizard-footer';
2
+
3
+ export default function ActionsFooter( props ) {
4
+ return (
5
+ <WizardFooter separator justify="end">
6
+ { props.children }
7
+ </WizardFooter>
8
+ );
9
+ }
10
+
11
+ ActionsFooter.propTypes = {
12
+ children: PropTypes.any,
13
+ };
app/modules/import-export/assets/js/shared/content-layout/content-layout.js ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import './content-layout.scss';
2
+
3
+ export default function ContentLayout( props ) {
4
+ return (
5
+ <div className="e-app-import-export-content-layout">
6
+ <div className="e-app-import-export-content-layout__container">
7
+ { props.children }
8
+ </div>
9
+ </div>
10
+ );
11
+ }
12
+
13
+ ContentLayout.propTypes = {
14
+ children: PropTypes.any.isRequired,
15
+ };
app/modules/import-export/assets/js/shared/content-layout/content-layout.scss ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ .e-app-import-export-content-layout {
2
+ display: flex;
3
+ justify-content: center;
4
+ height: 100%;
5
+
6
+ &__container {
7
+ flex-basis: 1075px;
8
+ }
9
+ }
app/modules/import-export/assets/js/shared/cpt-select-box/cpt-object-to-options-array.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export const cptObjectToOptionsArray = ( cptObject, label = 'label' ) => {
2
+ const cptOptionsArray = [];
3
+ // eslint-disable-next-line no-unused-expressions
4
+ if ( cptObject && label ) {
5
+ Object.keys( cptObject ).forEach( ( key ) => cptOptionsArray.push( {
6
+ label: cptObject[ key ][ label ],
7
+ value: key,
8
+ } ) );
9
+ }
10
+ return cptOptionsArray;
11
+ };
app/modules/import-export/assets/js/shared/cpt-select-box/cpt-select-box.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useContext, useState, useEffect } from 'react';
2
+ import { SharedContext } from '../../context/shared-context/shared-context-provider';
3
+ import Select2 from 'elementor-app/ui/molecules/select2';
4
+ import Text from 'elementor-app/ui/atoms/text';
5
+ import TextField from 'elementor-app/ui/atoms/text-field';
6
+
7
+ export default function CptSelectBox() {
8
+ const sharedContext = useContext( SharedContext ),
9
+ { customPostTypes } = sharedContext.data || [],
10
+ [ selected, setSelected ] = useState( [] );
11
+
12
+ useEffect( () => {
13
+ setSelected( arrayValueIterator( customPostTypes ) );
14
+ }, [ customPostTypes ] );
15
+
16
+ useEffect( () => {
17
+ sharedContext.dispatch( { type: 'SET_SELECTED_CPT', payload: selected } );
18
+ }, [ selected ] );
19
+
20
+ const arrayValueIterator = ( array ) => {
21
+ return array.map( ( { value } ) => value );
22
+ };
23
+
24
+ const selectedCpt = ( selectedValue ) => {
25
+ setSelected( arrayValueIterator( Array.from( selectedValue ) ) );
26
+ };
27
+
28
+ return (
29
+ <>
30
+ <Text variant="sm" tag="p" className="e-app-export-kit-content__description">
31
+ { __( 'Custom Post Type', 'elementor' ) }
32
+ </Text>
33
+ { customPostTypes.length > 0
34
+ ? <Select2
35
+ multiple
36
+ settings={ { width: '100%' } }
37
+ options={ customPostTypes }
38
+ onChange={ ( e ) => selectedCpt( e.target.selectedOptions ) }
39
+ value={ selected }
40
+ placeholder={ __( 'Click to select custom post types', 'elementor' ) }
41
+ />
42
+ : <TextField
43
+ variant="outlined"
44
+ // eslint-disable-next-line @wordpress/i18n-ellipsis
45
+ placeholder={ __( 'No custom post types in your site...', 'elementor' ) }
46
+ className="e-app-export-kit-content__disabled"
47
+ />
48
+ }
49
+ <Text variant="sm" tag="span" className="e-app-export-kit-content__small-notice">
50
+ { __( 'Add the custom posts types to export. The latest 20 items from each type will be included.', 'elementor' ) }
51
+ </Text>
52
+ </>
53
+ );
54
+ }
app/modules/import-export/assets/js/shared/file-process/file-process.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import ProcessFailedDialog from '../process-failed-dialog/process-failed-dialog';
4
+ import WizardStep from '../../ui/wizard-step/wizard-step';
5
+
6
+ export default function FileProcess( props ) {
7
+ return (
8
+ <WizardStep
9
+ className={ arrayToClassName( [ 'e-app-import-export-file-process', props.className ] ) }
10
+ icon="eicon-loading eicon-animation-spin"
11
+ // eslint-disable-next-line @wordpress/i18n-ellipsis
12
+ heading={ __( 'Setting up your kit...', 'elementor' ) }
13
+ description={
14
+ <>
15
+ { __( 'This usually takes a few moments.', 'elementor' ) }
16
+ <br />
17
+ { __( "Don't close this window until the process is finished.", 'elementor' ) }
18
+ </>
19
+ }
20
+ info={ props.info }
21
+ >
22
+ { ! ! props.errorType &&
23
+ <ProcessFailedDialog
24
+ onApprove={ props.onDialogApprove }
25
+ onDismiss={ props.onDialogDismiss }
26
+ errorType={ props.errorType }
27
+ />
28
+ }
29
+ </WizardStep>
30
+ );
31
+ }
32
+
33
+ FileProcess.propTypes = {
34
+ className: PropTypes.string,
35
+ onDialogApprove: PropTypes.func,
36
+ onDialogDismiss: PropTypes.func,
37
+ errorType: PropTypes.string,
38
+ info: PropTypes.string,
39
+ };
40
+
41
+ FileProcess.defaultProps = {
42
+ className: '',
43
+ };
app/modules/import-export/assets/js/shared/info-modal/export-info-modal.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import InlineLink from 'elementor-app/ui/molecules/inline-link';
2
+ import InfoModal from './info-modal';
3
+
4
+ export default function ExportInfoModal( props ) {
5
+ return (
6
+ <InfoModal { ...props } title={ __( 'Export a Website Kit', 'elementor' ) }>
7
+ <InfoModal.Section>
8
+ <InfoModal.Heading>{ __( 'What’s a Website Kit?', 'elementor' ) }</InfoModal.Heading>
9
+ <InfoModal.Text>
10
+ <>
11
+ { __( 'A Website Kit is a .zip file that contains all the parts of a complete site. It’s an easy way to get a site up and running quickly.', 'elementor' ) }
12
+ <br /><br />
13
+ <InlineLink url="https://go.elementor.com/app-what-are-kits">{ __( ' Learn more about Website Kits', 'elementor' ) }</InlineLink>
14
+ </>
15
+ </InfoModal.Text>
16
+ </InfoModal.Section>
17
+
18
+ <InfoModal.Section>
19
+ <InfoModal.Heading>{ __( 'How does exporting work?', 'elementor' ) }</InfoModal.Heading>
20
+ <InfoModal.Text>
21
+ <>
22
+ { __( 'To turn your site into a Website Kit, select the templates, content, settings and plugins you want to include. Once it’s ready, you’ll get a .zip file that you can import to other sites.', 'elementor' ) }
23
+ <br /><br />
24
+ <InlineLink url="http://go.elementor.com/app-export-kit">{ __( 'Learn More', 'elementor' ) }</InlineLink>
25
+ </>
26
+ </InfoModal.Text>
27
+ </InfoModal.Section>
28
+ </InfoModal>
29
+ );
30
+ }
app/modules/import-export/assets/js/shared/info-modal/import-info-modal.js ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import InlineLink from 'elementor-app/ui/molecules/inline-link';
2
+ import InfoModal from './info-modal';
3
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
4
+
5
+ export default function ImportInfoModal( props ) {
6
+ const eventTracking = ( element ) => appsEventTrackingDispatch(
7
+ 'kit-library/seek-more-info',
8
+ {
9
+ page_source: 'import',
10
+ modal_type: 'info',
11
+ event_type: 'click',
12
+ element,
13
+ },
14
+ );
15
+ return (
16
+ <InfoModal { ...props } title={ __( 'Import a Website Kit', 'elementor' ) }>
17
+ <InfoModal.Section>
18
+ <InfoModal.Heading>{ __( 'What’s a Website Kit?', 'elementor' ) }</InfoModal.Heading>
19
+ <InfoModal.Text>
20
+ <>
21
+ { __( 'A Website Kit is a .zip file that contains all the parts of a complete site. It’s an easy way to get a site up and running quickly.', 'elementor' ) }
22
+ <br /><br />
23
+ <InlineLink
24
+ url="https://go.elementor.com/app-what-are-kits"
25
+ onClick={ () => eventTracking( 'Learn more about website kits' ) }
26
+ >
27
+ { __( ' Learn more about Website Kits', 'elementor' ) }
28
+ </InlineLink>
29
+ </>
30
+ </InfoModal.Text>
31
+ </InfoModal.Section>
32
+
33
+ <InfoModal.Section>
34
+ <InfoModal.Heading>{ __( 'How does importing work?', 'elementor' ) }</InfoModal.Heading>
35
+ <InfoModal.Text>
36
+ <>
37
+ { __( 'Start by uploading the file and selecting the parts and plugins you want to apply. If there are any overlaps between the kit and your current design, you’ll be able to choose which imported parts you want to apply or ignore. Once the file is ready, the kit will be applied to your site and you’ll be able to see it live.', 'elementor' ) }
38
+ <br /><br />
39
+ <InlineLink
40
+ url="http://go.elementor.com/app-import-kit"
41
+ onClick={ () => eventTracking( 'learn more' ) }
42
+ >
43
+ { __( 'Learn More', 'elementor' ) }
44
+ </InlineLink>
45
+ </>
46
+ </InfoModal.Text>
47
+ </InfoModal.Section>
48
+ </InfoModal>
49
+ );
50
+ }
app/modules/import-export/assets/js/shared/info-modal/info-modal-heading.js ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import Heading from 'elementor-app/ui/atoms/heading';
4
+
5
+ export default function InfoModalHeading( props ) {
6
+ return (
7
+ <Heading variant="h3" tag="h2" className={ arrayToClassName( [ 'e-app-import-export-info-modal__heading', props.className ] ) }>
8
+ { props.children }
9
+ </Heading>
10
+ );
11
+ }
12
+
13
+ InfoModalHeading.propTypes = {
14
+ className: PropTypes.string,
15
+ children: PropTypes.oneOfType( [
16
+ PropTypes.string,
17
+ PropTypes.object,
18
+ PropTypes.arrayOf( PropTypes.object ),
19
+ ] ).isRequired,
20
+ };
21
+
22
+ InfoModalHeading.defaultProps = {
23
+ className: '',
24
+ };
app/modules/import-export/assets/js/shared/info-modal/info-modal-section.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import ModalProvider from 'elementor-app/ui/modal/modal';
4
+
5
+ export default function InfoModalSection( props ) {
6
+ return (
7
+ <ModalProvider.Section className={ arrayToClassName( [ 'e-app-import-export-info-modal__section', props.className ] ) }>
8
+ { props.children }
9
+ </ModalProvider.Section>
10
+ );
11
+ }
12
+
13
+ InfoModalSection.propTypes = {
14
+ className: PropTypes.string,
15
+ children: PropTypes.any,
16
+ };
17
+
18
+ InfoModalSection.defaultProps = {
19
+ className: '',
20
+ };
app/modules/import-export/assets/js/shared/info-modal/info-modal-text.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import Text from 'elementor-app/ui/atoms/text';
4
+
5
+ export default function InfoModalText( props ) {
6
+ return (
7
+ <Text variant="sm" className={ arrayToClassName( [ 'e-app-import-export-info-modal__text', props.className ] ) }>
8
+ { props.children }
9
+ </Text>
10
+ );
11
+ }
12
+
13
+ InfoModalText.propTypes = {
14
+ className: PropTypes.string,
15
+ children: PropTypes.any.isRequired,
16
+ };
17
+
18
+ InfoModalText.defaultProps = {
19
+ className: '',
20
+ };
app/modules/import-export/assets/js/shared/info-modal/info-modal-tip.js ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import ModalProvider from 'elementor-app/ui/modal/modal';
4
+
5
+ export default function InfoModalTip( props ) {
6
+ return <ModalProvider.Tip { ...props } className={ arrayToClassName( [ 'e-app-import-export-info-modal__tip', props.className ] ) } />;
7
+ }
8
+
9
+ InfoModalTip.propTypes = {
10
+ className: PropTypes.string,
11
+ };
12
+
13
+ InfoModalTip.defaultProps = {
14
+ className: '',
15
+ };
app/modules/import-export/assets/js/shared/info-modal/info-modal.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ModalProvider from 'elementor-app/ui/modal/modal';
2
+ import InfoModalSection from './info-modal-section';
3
+ import InfoModalHeading from './info-modal-heading';
4
+ import InfoModalText from './info-modal-text';
5
+ import InfoModalTip from './info-modal-tip';
6
+
7
+ import './info-modal.scss';
8
+
9
+ export const infoButtonProps = {
10
+ id: 'info-modal',
11
+ className: 'e-app-export-kit-information__info-icon',
12
+ icon: 'eicon-info-circle',
13
+ text: __( 'Kit Info', 'elementor' ),
14
+ color: 'secondary',
15
+ hideText: true,
16
+ };
17
+
18
+ export default function InfoModal( props ) {
19
+ const attrs = {
20
+ className: 'e-app-import-export-info-modal',
21
+ setShow: props.setShow,
22
+ onOpen: props.onOpen,
23
+ onClose: props.onClose,
24
+ referrer: props.referrer,
25
+ };
26
+
27
+ if ( Object.prototype.hasOwnProperty.call( props, 'show' ) ) {
28
+ attrs.show = props.show;
29
+ } else {
30
+ attrs.toggleButtonProps = infoButtonProps;
31
+ }
32
+
33
+ return (
34
+ <ModalProvider { ...attrs } title={ props.title }>
35
+ { props.children }
36
+ </ModalProvider>
37
+ );
38
+ }
39
+
40
+ InfoModal.propTypes = {
41
+ show: PropTypes.bool,
42
+ setShow: PropTypes.func,
43
+ title: PropTypes.string,
44
+ children: PropTypes.any.isRequired,
45
+ onOpen: PropTypes.func,
46
+ onClose: PropTypes.func,
47
+ referrer: PropTypes.string,
48
+ };
49
+
50
+ InfoModal.Section = InfoModalSection;
51
+ InfoModal.Heading = InfoModalHeading;
52
+ InfoModal.Text = InfoModalText;
53
+ InfoModal.Tip = InfoModalTip;
54
+
app/modules/import-export/assets/js/shared/info-modal/info-modal.scss ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ .e-app-import-export-info-modal {
2
+ &__section:not(:first-child) {
3
+ margin-top: spacing(30);
4
+ }
5
+
6
+ &__heading {
7
+ margin-bottom: spacing(20);
8
+ }
9
+ }
app/modules/import-export/assets/js/shared/kit-content-data/kit-content-data.js ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const kitContentData = [
2
+ {
3
+ type: 'templates',
4
+ data: {
5
+ title: __( 'Templates', 'elementor' ),
6
+ features: {
7
+ open: [
8
+ __( 'Saved Templates', 'elementor' ),
9
+ ],
10
+ locked: [
11
+ __( 'Headers', 'elementor' ),
12
+ __( 'Footers', 'elementor' ),
13
+ __( 'Archives', 'elementor' ),
14
+ __( 'Single Posts', 'elementor' ),
15
+ __( 'Single Pages', 'elementor' ),
16
+ __( 'Search Results', 'elementor' ),
17
+ __( '404 Error Page', 'elementor' ),
18
+ __( 'Popups', 'elementor' ),
19
+ __( 'Global widgets', 'elementor' ),
20
+ ],
21
+ tooltip: __( 'To import or export these components, you’ll need Elementor Pro.', 'elementor' ),
22
+ },
23
+ },
24
+ },
25
+ {
26
+ type: 'content',
27
+ data: {
28
+ title: __( 'Content', 'elementor' ),
29
+ features: {
30
+ open: [
31
+ __( 'Elementor Pages', 'elementor' ),
32
+ __( 'Landing Pages', 'elementor' ),
33
+ __( 'Elementor Posts', 'elementor' ),
34
+ __( 'WP Pages', 'elementor' ),
35
+ __( 'WP Posts', 'elementor' ),
36
+ __( 'WP Menus', 'elementor' ),
37
+ __( 'Custom Post Types', 'elementor' ),
38
+ ],
39
+ },
40
+ },
41
+ },
42
+ {
43
+ type: 'settings',
44
+ data: {
45
+ title: __( 'Site Settings', 'elementor' ),
46
+ features: {
47
+ open: [
48
+ __( 'Global Colors', 'elementor' ),
49
+ __( 'Global Fonts', 'elementor' ),
50
+ __( 'Theme Style settings', 'elementor' ),
51
+ __( 'Layout Settings', 'elementor' ),
52
+ __( 'Lightbox Settings', 'elementor' ),
53
+ __( 'Background Settings', 'elementor' ),
54
+ ],
55
+ },
56
+ },
57
+ },
58
+ ];
59
+
60
+ export default kitContentData;
app/modules/import-export/assets/js/shared/kit-content/components/kit-content-checkbox/kit-content-checkbox.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext, useMemo, useEffect } from 'react';
2
+
3
+ import { SharedContext } from '../../../../context/shared-context/shared-context-provider';
4
+
5
+ import Checkbox from 'elementor-app/ui/atoms/checkbox';
6
+
7
+ export default function KitContentCheckbox( props ) {
8
+ const sharedContext = useContext( SharedContext ),
9
+ isSelected = () => sharedContext.data.includes.includes( props.type ),
10
+ setIncludes = ( event ) => {
11
+ const isChecked = event.target.checked,
12
+ actionType = isChecked ? 'ADD_INCLUDE' : 'REMOVE_INCLUDE';
13
+ props.onCheck?.( event, props.type );
14
+
15
+ sharedContext.dispatch( { type: actionType, payload: props.type } );
16
+ };
17
+
18
+ useEffect( () => {
19
+ if ( ! sharedContext.data.includes.length ) {
20
+ sharedContext.dispatch( { type: 'ADD_INCLUDE', payload: props.type } );
21
+ }
22
+ }, [] );
23
+
24
+ return useMemo( () => (
25
+ <Checkbox checked={ isSelected() } onChange={ setIncludes } className={ props.className } />
26
+ ), [ sharedContext.data.includes ] );
27
+ }
28
+
29
+ KitContentCheckbox.propTypes = {
30
+ className: PropTypes.string,
31
+ type: PropTypes.string.isRequired,
32
+ };
33
+
34
+ KitContentCheckbox.defaultProps = {
35
+ className: '',
36
+ };
app/modules/import-export/assets/js/shared/kit-content/components/templates-features/templates-features.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Tooltip from 'elementor-app/molecules/tooltip';
2
+
3
+ import './templates-features.scss';
4
+
5
+ export default function TemplatesFeatures( props ) {
6
+ const isLockedFeatures = props.features.locked?.length,
7
+ getLockedFeatures = () => {
8
+ if ( ! isLockedFeatures ) {
9
+ return;
10
+ }
11
+
12
+ return (
13
+ <Tooltip
14
+ tag="span"
15
+ offset={ 19 }
16
+ show={ props.showTooltip }
17
+ title={ props.features.tooltip }
18
+ disabled={ ! props.isLocked }
19
+ className={ props.isLocked ? 'e-app-export-templates-features__locked' : '' }
20
+ >
21
+ { ', ' + props.features.locked.join( ', ' ) }
22
+ </Tooltip>
23
+ );
24
+ },
25
+ getOpenFeatures = () => props.features.open?.join( ', ' );
26
+
27
+ return (
28
+ <>
29
+ { getOpenFeatures() }
30
+ { getLockedFeatures() }
31
+ </>
32
+ );
33
+ }
34
+
35
+ TemplatesFeatures.propTypes = {
36
+ features: PropTypes.object,
37
+ isLocked: PropTypes.bool,
38
+ showTooltip: PropTypes.bool,
39
+ };
40
+
41
+ TemplatesFeatures.defaultProps = {
42
+ showTooltip: false,
43
+ };
app/modules/import-export/assets/js/shared/kit-content/components/templates-features/templates-features.scss ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-export-templates-features-locked-color: tints(500);
2
+ $e-app-export-templates-features-locked-dark-color: dark-tints(300);
3
+
4
+ $root: e-app-export-templates-features__locked;
5
+
6
+ .#{$root} {
7
+ --e-app-export-templates-features-locked-color: #{$e-app-export-templates-features-locked-color};
8
+
9
+ color: var(--e-app-export-templates-features-locked-color);
10
+ }
11
+
12
+ .eps-theme-dark {
13
+ .#{$root} {
14
+ --e-app-export-templates-features-locked-color: #{$e-app-export-templates-features-locked-dark-color};
15
+ }
16
+ }
app/modules/import-export/assets/js/shared/kit-content/kit-content.js ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useContext } from 'react';
2
+
3
+ import TemplatesFeatures from './components/templates-features/templates-features';
4
+ import KitContentCheckbox from './components/kit-content-checkbox/kit-content-checkbox';
5
+ import CptOptionsSelectBox from '../cpt-select-box/cpt-select-box';
6
+ import GoProButton from 'elementor-app/molecules/go-pro-button';
7
+ import Box from 'elementor-app/ui/atoms/box';
8
+ import List from 'elementor-app/ui/molecules/list';
9
+ import Heading from 'elementor-app/ui/atoms/heading';
10
+ import Text from 'elementor-app/ui/atoms/text';
11
+ import Grid from 'elementor-app/ui/grid/grid';
12
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
13
+ import { SharedContext } from './../../context/shared-context/shared-context-provider.js';
14
+
15
+ import './kit-content.scss';
16
+
17
+ export default function KitContent( { contentData, hasPro } ) {
18
+ const [ containerHover, setContainerHover ] = useState( {} ),
19
+ sharedContext = useContext( SharedContext ),
20
+ { referrer, currentPage } = sharedContext.data,
21
+ // Need to read the hasPro value first from the props because the plugin might be installed during the process.
22
+ isProExist = hasPro || elementorAppConfig.hasPro,
23
+ getTemplateFeatures = ( features, index ) => {
24
+ if ( ! features ) {
25
+ return;
26
+ }
27
+
28
+ return (
29
+ <TemplatesFeatures
30
+ features={ features }
31
+ isLocked={ ! isProExist }
32
+ showTooltip={ containerHover[ index ] }
33
+ />
34
+ );
35
+ },
36
+ setContainerHoverState = ( index, state ) => {
37
+ setContainerHover( ( prevState ) => ( { ...prevState, [ index ]: state } ) );
38
+ },
39
+ eventTracking = ( event, chosenPart ) => {
40
+ if ( 'kit-library' === referrer ) {
41
+ const command = event.target.checked && event.target.checked ? 'check' : 'uncheck';
42
+ appsEventTrackingDispatch(
43
+ `kit-library/${ command }`,
44
+ {
45
+ page_source: 'import',
46
+ step: currentPage,
47
+ event_type: 'click',
48
+ site_part: chosenPart,
49
+ },
50
+ );
51
+ }
52
+ };
53
+
54
+ if ( ! contentData.length ) {
55
+ return null;
56
+ }
57
+
58
+ return (
59
+ <Box>
60
+ <List separated className="e-app-export-kit-content">
61
+ {
62
+ contentData.map( ( { type, data }, index ) => {
63
+ const isLockedFeaturesNoPro = data.features?.locked && ! isProExist;
64
+ return (
65
+ <List.Item padding="20" key={ type } className="e-app-export-kit-content__item">
66
+ <div
67
+ onMouseEnter={ () => isLockedFeaturesNoPro && setContainerHoverState( index, true ) }
68
+ onMouseLeave={ () => isLockedFeaturesNoPro && setContainerHoverState( index, false ) }
69
+ >
70
+ <Grid container noWrap >
71
+ <KitContentCheckbox
72
+ type={ type }
73
+ className="e-app-export-kit-content__checkbox"
74
+ onCheck={ ( event, chosenPart ) => {
75
+ eventTracking( event, chosenPart );
76
+ } }
77
+ />
78
+
79
+ <Grid item container>
80
+ <Heading variant="h4" tag="h3" className="e-app-export-kit-content__title">
81
+ { data.title }
82
+ </Heading>
83
+
84
+ <Grid item container direction={ isLockedFeaturesNoPro ? 'row' : 'column' } alignItems={ 'baseline' } >
85
+ <Text variant="sm" tag="p" className="e-app-export-kit-content__description">
86
+ { data.description || getTemplateFeatures( data.features, index ) }
87
+ </Text>
88
+ { 'content' === type && <CptOptionsSelectBox /> }
89
+ {
90
+ isLockedFeaturesNoPro &&
91
+ <GoProButton
92
+ className="e-app-export-kit-content__go-pro-button"
93
+ url="https://go.elementor.com/go-pro-import-export"
94
+ />
95
+ }
96
+ </Grid>
97
+ </Grid>
98
+ </Grid>
99
+ </div>
100
+ </List.Item>
101
+ );
102
+ } )
103
+ }
104
+ </List>
105
+ </Box>
106
+ );
107
+ }
108
+
109
+ KitContent.propTypes = {
110
+ className: PropTypes.string,
111
+ contentData: PropTypes.array.isRequired,
112
+ hasPro: PropTypes.bool,
113
+ };
114
+
115
+ KitContent.defaultProps = {
116
+ className: '',
117
+ };
app/modules/import-export/assets/js/shared/kit-content/kit-content.scss ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-export-kit-content-title-color: tints(700);
2
+ $e-app-export-kit-content-title-dark-color: dark-tints(100);
3
+ $e-app-export-kit-content-description-color: tints(600);
4
+ $e-app-export-kit-content-description-dark-color: dark-tints(200);
5
+
6
+ $root: e-app-export-kit-content;
7
+
8
+ .#{$root} {
9
+ --e-app-export-kit-content-title-color: #{$e-app-export-kit-content-title-color};
10
+ --e-app-export-kit-content-description-color: #{$e-app-export-kit-content-description-color};
11
+
12
+ &__checkbox {
13
+ flex-shrink: 0;
14
+ margin-inline-end: spacing(12);
15
+ }
16
+
17
+ &__title {
18
+ color: var(--e-app-export-kit-content-title-color);
19
+ }
20
+
21
+ &__description {
22
+ color: var(--e-app-export-kit-content-description-color);
23
+ margin-inline-end: spacing(20);
24
+ }
25
+
26
+ &__notice {
27
+ margin-top: spacing(16);
28
+ }
29
+
30
+ &__small-notice {
31
+ font-style: italic;
32
+ color: var(--e-app-export-kit-content-description-color);
33
+ }
34
+ }
35
+
36
+ .eps-theme-dark {
37
+ .#{$root} {
38
+ --e-app-export-kit-content-title-color: #{$e-app-export-kit-content-title-dark-color};
39
+ --e-app-export-kit-content-description-color: #{$e-app-export-kit-content-description-dark-color};
40
+ }
41
+ }
app/modules/import-export/assets/js/shared/kit-data/components/included/included.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Text from 'elementor-app/ui/atoms/text';
2
+
3
+ export default function Included( { data } ) {
4
+ return (
5
+ <Text className="e-app-import-export-kit-data__included">
6
+ { data.filter( ( value ) => value ).join( ' | ' ) }
7
+ </Text>
8
+ );
9
+ }
10
+
11
+ Included.propTypes = {
12
+ data: PropTypes.array,
13
+ };
app/modules/import-export/assets/js/shared/kit-data/components/site-area/site-area.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Text from 'elementor-app/ui/atoms/text';
2
+ import Icon from 'elementor-app/ui/atoms/icon';
3
+ import InlineLink from 'elementor-app/ui/molecules/inline-link';
4
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
5
+
6
+ export default function SiteArea( { text, link } ) {
7
+ const eventTracking = ( command, eventType = 'click' ) => {
8
+ appsEventTrackingDispatch(
9
+ command,
10
+ {
11
+ site_area: text,
12
+ page_source: 'import complete',
13
+ event_type: eventType,
14
+ },
15
+ );
16
+ };
17
+
18
+ return (
19
+ <InlineLink url={ link } color="secondary" underline="none" onClick={ () => eventTracking( 'kit-library/open-site-area' ) }>
20
+ <Text className="e-app-import-export-kit-data__site-area">
21
+ { text } { link && <Icon className="eicon-editor-external-link" /> }
22
+ </Text>
23
+ </InlineLink>
24
+ );
25
+ }
26
+
27
+ SiteArea.propTypes = {
28
+ text: PropTypes.string,
29
+ link: PropTypes.string,
30
+ };
app/modules/import-export/assets/js/shared/kit-data/hooks/use-kit-data.js ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMemo } from 'react';
2
+
3
+ export default function useKitData( kitData ) {
4
+ const getLabel = ( type, key, amount ) => {
5
+ // The summary-titles data will not exist in the kitData as part of the export process, and therefore should be taken from the elementorAppConfig.
6
+ const summaryTitlesData = kitData?.configData?.summaryTitles || elementorAppConfig[ 'import-export' ].summaryTitles,
7
+ label = summaryTitlesData[ type ][ key ];
8
+
9
+ if ( label?.single ) {
10
+ if ( ! amount ) {
11
+ return '';
12
+ }
13
+
14
+ const title = amount > 1 ? label.plural : label.single;
15
+
16
+ return amount + ' ' + title;
17
+ }
18
+
19
+ return label;
20
+ },
21
+ getTemplates = () => {
22
+ const templates = {};
23
+
24
+ for ( const key in kitData?.templates ) {
25
+ const type = kitData.templates[ key ].doc_type;
26
+
27
+ if ( ! templates[ type ] ) {
28
+ templates[ type ] = 0;
29
+ }
30
+
31
+ templates[ type ]++;
32
+ }
33
+
34
+ return Object
35
+ .entries( templates )
36
+ .map( ( [ key, amount ] ) => getLabel( 'templates', key, amount ) )
37
+ .filter( ( value ) => value );
38
+ },
39
+ getSiteSettings = () => {
40
+ const siteSettings = kitData?.[ 'site-settings' ] || {};
41
+
42
+ return Object
43
+ .values( siteSettings )
44
+ .map( ( item ) => getLabel( 'site-settings', item ) );
45
+ },
46
+ getContent = () => {
47
+ const content = kitData?.content || {},
48
+ wpContent = kitData?.[ 'wp-content' ] || {};
49
+
50
+ let mergedContent = { ...content };
51
+
52
+ for ( const key in mergedContent ) {
53
+ mergedContent[ key ] = Object.keys( mergedContent[ key ] ).concat( wpContent[ key ] || [] );
54
+ }
55
+
56
+ // In case that wpContent has properties that doesn't exist in the content object.
57
+ mergedContent = { ...wpContent, ...mergedContent };
58
+
59
+ return Object
60
+ .entries( mergedContent )
61
+ .map( ( [ key, data ] ) => getLabel( 'content', key, data.length ) )
62
+ .filter( ( value ) => value );
63
+ },
64
+ getPlugins = () => {
65
+ return kitData?.plugins ? kitData.plugins.map( ( { name } ) => name ) : [];
66
+ };
67
+
68
+ return useMemo( () => ( {
69
+ templates: getTemplates(),
70
+ siteSettings: getSiteSettings(),
71
+ content: getContent(),
72
+ plugins: getPlugins(),
73
+ } ), [ kitData ] );
74
+ }
app/modules/import-export/assets/js/shared/kit-data/kit-data.js ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { memo } from 'react';
2
+
3
+ import SiteArea from './components/site-area/site-area';
4
+ import Included from './components/included/included';
5
+ import DataTable from 'elementor-app/molecules/data-table';
6
+
7
+ import useKitData from './hooks/use-kit-data';
8
+
9
+ import './kit-data.scss';
10
+
11
+ const siteEditorPath = elementorAppConfig.hasPro ? '#/site-editor' : '#/site-editor/promotion';
12
+
13
+ function KitData( { data } ) {
14
+ const { templates, siteSettings, content, plugins } = useKitData( data ),
15
+ { elementorHomePageUrl, recentlyEditedElementorPageUrl } = data?.configData || elementorAppConfig[ 'import-export' ],
16
+ siteSettingsUrl = elementorHomePageUrl || recentlyEditedElementorPageUrl,
17
+ headers = [
18
+ __( 'Site Area', 'elementor' ),
19
+ __( 'Included', 'elementor' ),
20
+ ],
21
+ rowsData = [
22
+ {
23
+ siteArea: __( 'Elementor Templates', 'elementor' ),
24
+ link: elementorAppConfig.base_url + siteEditorPath,
25
+ included: templates,
26
+ },
27
+ {
28
+ siteArea: __( 'Site Settings', 'elementor' ),
29
+ link: siteSettingsUrl ? siteSettingsUrl + '#e:run:panel/global/open' : '',
30
+ included: siteSettings,
31
+ },
32
+ {
33
+ siteArea: __( 'Content', 'elementor' ),
34
+ link: elementorAppConfig.admin_url + 'edit.php?post_type=page',
35
+ included: content,
36
+ },
37
+ {
38
+ siteArea: __( 'Plugins', 'elementor' ),
39
+ link: elementorAppConfig.admin_url + 'plugins.php',
40
+ included: plugins,
41
+ },
42
+ ],
43
+ rows = rowsData
44
+ .map( ( { siteArea, included, link } ) => {
45
+ if ( ! included.length ) {
46
+ // eslint-disable-next-line array-callback-return
47
+ return;
48
+ }
49
+
50
+ return [
51
+ <SiteArea key={ siteArea } text={ siteArea } link={ link } />,
52
+ <Included key={ included } data={ included } />,
53
+ ];
54
+ } )
55
+ .filter( ( row ) => row );
56
+
57
+ if ( ! rows.length ) {
58
+ return null;
59
+ }
60
+
61
+ return (
62
+ <DataTable
63
+ className="e-app-import-export-kit-data"
64
+ headers={ headers }
65
+ rows={ rows }
66
+ layout={ [ 1, 3 ] }
67
+ />
68
+ );
69
+ }
70
+
71
+ KitData.propTypes = {
72
+ data: PropTypes.object,
73
+ };
74
+
75
+ export default memo( KitData );
app/modules/import-export/assets/js/shared/kit-data/kit-data.scss ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-import-export-kit-data-site-area-color: tints(700);
2
+ $e-app-import-export-kit-data-site-area-dark-color: dark-tints(200);
3
+ $e-app-import-export-kit-data-included-color: tints(500);
4
+ $e-app-import-export-kit-data-included-dark-color: dark-tints(300);
5
+
6
+ $root: e-app-import-export-kit-data;
7
+
8
+ .#{$root} {
9
+ --e-app-import-export-kit-data-site-area-color: #{$e-app-import-export-kit-data-site-area-color};
10
+ --e-app-import-export-kit-data-included-color: #{$e-app-import-export-kit-data-included-color};
11
+
12
+ &__site-area,
13
+ &__included {
14
+ margin-bottom: 0;
15
+ }
16
+
17
+ &__site-area {
18
+ color: var(--e-app-import-export-kit-data-site-area-color);
19
+ font-weight: bold;
20
+ }
21
+
22
+ &__included {
23
+ color: var(--e-app-import-export-kit-data-included-color);
24
+ }
25
+ }
26
+
27
+ .eps-theme-dark {
28
+ .#{$root} {
29
+ --e-app-import-export-kit-data-site-area-color: #{$e-app-import-export-kit-data-site-area-dark-color};
30
+ --e-app-import-export-kit-data-included-color: #{$e-app-import-export-kit-data-included-dark-color};
31
+ }
32
+ }
app/modules/import-export/assets/js/shared/plugins-selection/components/plugins-table.js ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { memo } from 'react';
2
+
3
+ import DataTable from 'elementor-app/molecules/data-table';
4
+
5
+ import Text from 'elementor-app/ui/atoms/text';
6
+ import InlineLink from 'elementor-app/ui/molecules/inline-link';
7
+ import Icon from 'elementor-app/ui/atoms/icon';
8
+
9
+ import './plugins-table.scss';
10
+
11
+ function PluginsTable( {
12
+ plugins,
13
+ layout,
14
+ withHeader,
15
+ withStatus,
16
+ onSelect,
17
+ initialSelected,
18
+ initialDisabled,
19
+ } ) {
20
+ const CellText = ( cellTextProps ) => (
21
+ <Text className="e-app-import-export-plugins-table__cell-content">
22
+ { cellTextProps.text }
23
+ </Text>
24
+ ),
25
+ CellLink = ( cellLinkProps ) => (
26
+ <InlineLink url={ cellLinkProps.url } underline="none">
27
+ { `${ __( 'Version' ) } ${ cellLinkProps.text }` } <Icon className="eicon-editor-external-link" />
28
+ </InlineLink>
29
+ ),
30
+ getHeaders = () => {
31
+ if ( ! withHeader ) {
32
+ return [];
33
+ }
34
+
35
+ const headers = [ 'Plugin Name', 'Version' ];
36
+
37
+ if ( withStatus ) {
38
+ headers.splice( 1, 0, 'Status' );
39
+ }
40
+
41
+ return headers;
42
+ },
43
+ rows = plugins.map( ( { name, status, version, plugin_uri: pluginUrl } ) => {
44
+ const row = [
45
+ <CellText text={ name } key={ name } />,
46
+ <CellLink text={ version } url={ pluginUrl } key={ name } />,
47
+ ];
48
+
49
+ if ( withStatus ) {
50
+ row.splice( 1, 0, <CellText text={ status } key={ name } /> );
51
+ }
52
+
53
+ return row;
54
+ } );
55
+
56
+ return (
57
+ <DataTable
58
+ selection
59
+ headers={ getHeaders() }
60
+ rows={ rows }
61
+ onSelect={ onSelect }
62
+ initialSelected={ initialSelected }
63
+ initialDisabled={ initialDisabled }
64
+ layout={ layout }
65
+ className="e-app-import-export-plugins-table"
66
+ />
67
+ );
68
+ }
69
+
70
+ PluginsTable.propTypes = {
71
+ onSelect: PropTypes.func,
72
+ initialDisabled: PropTypes.array,
73
+ initialSelected: PropTypes.array,
74
+ plugins: PropTypes.array,
75
+ withHeader: PropTypes.bool,
76
+ withStatus: PropTypes.bool,
77
+ layout: PropTypes.array,
78
+ };
79
+
80
+ PluginsTable.defaultProps = {
81
+ initialDisabled: [],
82
+ initialSelected: [],
83
+ plugins: [],
84
+ withHeader: true,
85
+ withStatus: true,
86
+ };
87
+
88
+ export default memo( PluginsTable );
app/modules/import-export/assets/js/shared/plugins-selection/components/plugins-table.scss ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ $root: e-app-import-export-plugins-table;
2
+
3
+ .#{$root} {
4
+ &__cell-content {
5
+ margin-bottom: 0;
6
+ text-transform: capitalize;
7
+ }
8
+ }
app/modules/import-export/assets/js/shared/plugins-selection/plugins-selection.js ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMemo, memo } from 'react';
2
+
3
+ import PluginsTable from './components/plugins-table';
4
+
5
+ function PluginsSelection( {
6
+ plugins,
7
+ initialSelected,
8
+ initialDisabled,
9
+ withHeader,
10
+ withStatus,
11
+ layout,
12
+ onSelect,
13
+ } ) {
14
+ if ( ! plugins.length ) {
15
+ return null;
16
+ }
17
+
18
+ // eslint-disable-next-line react-hooks/rules-of-hooks
19
+ const cachedPlugins = useMemo( () => plugins, [ plugins ] ),
20
+ // eslint-disable-next-line react-hooks/rules-of-hooks
21
+ cachedInitialSelected = useMemo( () => initialSelected, [ plugins ] ),
22
+ // eslint-disable-next-line react-hooks/rules-of-hooks
23
+ cachedInitialDisabled = useMemo( () => initialDisabled, [ plugins ] ),
24
+ handleOnSelect = ( selectedIndexes ) => {
25
+ if ( ! onSelect ) {
26
+ return;
27
+ }
28
+
29
+ const selectedPlugins = selectedIndexes.map( ( pluginIndex ) => plugins[ pluginIndex ] );
30
+
31
+ onSelect( selectedPlugins );
32
+ };
33
+
34
+ return (
35
+ <PluginsTable
36
+ plugins={ cachedPlugins }
37
+ initialDisabled={ cachedInitialDisabled }
38
+ initialSelected={ cachedInitialSelected }
39
+ onSelect={ handleOnSelect }
40
+ withHeader={ withHeader }
41
+ withStatus={ withStatus }
42
+ layout={ layout }
43
+ />
44
+ );
45
+ }
46
+
47
+ PluginsSelection.propTypes = {
48
+ initialDisabled: PropTypes.array,
49
+ initialSelected: PropTypes.array,
50
+ layout: PropTypes.array,
51
+ onSelect: PropTypes.func,
52
+ plugins: PropTypes.array,
53
+ selection: PropTypes.bool,
54
+ withHeader: PropTypes.bool,
55
+ withStatus: PropTypes.bool,
56
+ };
57
+
58
+ PluginsSelection.defaultProps = {
59
+ initialDisabled: [],
60
+ initialSelected: [],
61
+ plugins: [],
62
+ selection: true,
63
+ withHeader: true,
64
+ withStatus: true,
65
+ };
66
+
67
+ export default memo( PluginsSelection );
app/modules/import-export/assets/js/shared/process-failed-dialog/process-failed-dialog.js ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect } from 'react';
2
+ import { useNavigate } from '@reach/router';
3
+
4
+ import Dialog from 'elementor-app/ui/dialog/dialog';
5
+
6
+ import useQueryParams from 'elementor-app/hooks/use-query-params';
7
+ import useAction from 'elementor-app/hooks/use-action';
8
+
9
+ const messagesContent = {
10
+ general: {
11
+ text: __( 'Nothing to worry about, just try again. If the problem continues, head over to the Help Center.', 'elementor' ),
12
+ },
13
+ 'zip-archive-module-not-installed': {
14
+ text: __( 'Install a PHP zip on your server or contact your site host.', 'elementor' ),
15
+ },
16
+ 'manifest-error': {
17
+ text: __( 'There is an error with the manifest file. Try importing again with a new kit file.', 'elementor' ),
18
+ },
19
+ 'no-write-permissions': {
20
+ text: __( 'Elementor is not authorized to read or write from this file. Contact your site host.', 'elementor' ),
21
+ },
22
+ 'plugin-installation-permissions-error': {
23
+ text: __( 'This kit requires new plugin installation. Unfortunately, you do not have permissions to install new plugins. Contact your site host.', 'elementor' ),
24
+ },
25
+ },
26
+ dialogTitle = __( 'Something went wrong.', 'elementor' ),
27
+ tryAgainText = __( 'Try Again', 'elementor' );
28
+
29
+ export default function ProcessFailedDialog( { errorType, onApprove, onDismiss, approveButton, dismissButton, onModalClose, onError, onLearnMore } ) {
30
+ const action = useAction(),
31
+ navigate = useNavigate(),
32
+ { referrer } = useQueryParams().getAll(),
33
+ error = 'string' === typeof errorType && messagesContent[ errorType ] ? errorType : 'general',
34
+ { text } = messagesContent[ error ],
35
+ isTryAgainAction = 'general' === error && onApprove,
36
+ handleOnApprove = () => {
37
+ /*
38
+ * When the errorType is general, there should be an option to trigger the onApprove function.
39
+ * All other error messages should open the learn-more link.
40
+ */
41
+ if ( isTryAgainAction ) {
42
+ onApprove();
43
+ } else {
44
+ window.open( 'https://go.elementor.com/app-import-download-failed', '_blank' );
45
+ }
46
+ onLearnMore?.();
47
+ },
48
+ handleOnDismiss = ( event ) => {
49
+ if ( 'general' === error && onDismiss ) {
50
+ onDismiss();
51
+ } else if ( 'kit-library' === referrer ) {
52
+ onModalClose?.( event );
53
+ navigate( '/kit-library' );
54
+ } else {
55
+ action.backToDashboard();
56
+ }
57
+ };
58
+
59
+ useEffect( () => {
60
+ onError?.();
61
+ }, [] );
62
+
63
+ return (
64
+ <Dialog
65
+ title={ dialogTitle }
66
+ text={ text }
67
+ approveButtonColor="link"
68
+ approveButtonText={ isTryAgainAction ? tryAgainText : approveButton }
69
+ approveButtonOnClick={ handleOnApprove }
70
+ dismissButtonText={ dismissButton }
71
+ dismissButtonOnClick={ ( event ) => handleOnDismiss( event ) }
72
+ onClose={ handleOnDismiss }
73
+ />
74
+ );
75
+ }
76
+
77
+ ProcessFailedDialog.propTypes = {
78
+ onApprove: PropTypes.func,
79
+ onDismiss: PropTypes.func,
80
+ errorType: PropTypes.string,
81
+ approveButton: PropTypes.string,
82
+ dismissButton: PropTypes.string,
83
+ onModalClose: PropTypes.func,
84
+ onError: PropTypes.func,
85
+ onLearnMore: PropTypes.func,
86
+ };
87
+
88
+ ProcessFailedDialog.defaultProps = {
89
+ errorType: 'general',
90
+ approveButton: __( 'Learn More', 'elementor' ),
91
+ dismissButton: __( 'Close', 'elementor' ),
92
+ };
app/modules/import-export/assets/js/templates/layout.js ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useContext } from 'react';
2
+
3
+ import Page from 'elementor-app/layout/page';
4
+ import ContentLayout from '../shared/content-layout/content-layout';
5
+ import { infoButtonProps } from '../shared/info-modal/info-modal';
6
+ import ImportInfoModal from '../shared/info-modal/import-info-modal';
7
+ import ExportInfoModal from '../shared/info-modal/export-info-modal';
8
+ import { SharedContext } from '../context/shared-context/shared-context-provider';
9
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
10
+
11
+ import useQueryParams from 'elementor-app/hooks/use-query-params';
12
+
13
+ export default function Layout( props ) {
14
+ const [ showInfoModal, setShowInfoModal ] = useState( false ),
15
+ { referrer } = useQueryParams().getAll(),
16
+ sharedContext = useContext( SharedContext ),
17
+ { currentPage } = sharedContext.data,
18
+ eventTracking = ( command, elementPosition = null, element = null, eventType = 'click', modalType = null ) => {
19
+ if ( 'kit-library' === sharedContext.data.referrer || referrer ) {
20
+ appsEventTrackingDispatch(
21
+ command,
22
+ {
23
+ element,
24
+ page_source: 'import',
25
+ event_type: eventType,
26
+ step: currentPage,
27
+ element_position: elementPosition,
28
+ modal_type: modalType,
29
+ },
30
+ );
31
+ }
32
+ },
33
+ onModalClose = ( e, command ) => {
34
+ const element = e.target.classList.contains( 'eps-modal__overlay' ) ? 'overlay' : 'x';
35
+ eventTracking( command, element, null, 'info' );
36
+ },
37
+ getContent = () => {
38
+ let infoModalProps = {
39
+ show: showInfoModal,
40
+ setShow: setShowInfoModal,
41
+ };
42
+
43
+ if ( 'kit-library' === sharedContext.data.referrer || referrer ) {
44
+ infoModalProps = {
45
+ referrer,
46
+ ...infoModalProps,
47
+ onOpen: () => eventTracking( 'kit-library/modal-open', null, null, 'load', 'info' ),
48
+ onClose: ( e ) => onModalClose( e, 'kit-library/modal-close' ),
49
+ };
50
+ }
51
+
52
+ return (
53
+ <ContentLayout>
54
+ { props.children }
55
+ { 'import' === props.type ? <ImportInfoModal { ...infoModalProps } /> : <ExportInfoModal { ...infoModalProps } /> }
56
+ </ContentLayout>
57
+ );
58
+ },
59
+ getInfoButtonProps = () => {
60
+ return {
61
+ ...infoButtonProps,
62
+ onClick: () => {
63
+ eventTracking( 'kit-library/seek-more-info', 'app_header' );
64
+ setShowInfoModal( true );
65
+ },
66
+ };
67
+ },
68
+ onClose = () => {
69
+ eventTracking( 'kit-library/close', 'app_header', null, 'click' );
70
+ window.top.location = elementorAppConfig.admin_url;
71
+ },
72
+ config = {
73
+ title: 'import' === props.type ? __( 'Import', 'elementor' ) : __( 'Export', 'elementor' ),
74
+ headerButtons: [ getInfoButtonProps(), ...props.headerButtons ],
75
+ content: getContent(),
76
+ footer: props.footer,
77
+ onClose: () => onClose(),
78
+ },
79
+ moduleAdminTab = '#tab-import-export-kit';
80
+
81
+ // Targeting the return_url value to the import-export dedicated admin tab (only when there is no specific referrer).
82
+ if ( ! referrer && -1 === elementorAppConfig.return_url.indexOf( moduleAdminTab ) && elementorAppConfig.return_url.includes( 'page=elementor-tools' ) ) {
83
+ elementorAppConfig.return_url += moduleAdminTab;
84
+ }
85
+
86
+ useEffect( () => {
87
+ if ( referrer ) {
88
+ sharedContext.dispatch( { type: 'SET_REFERRER', payload: referrer } );
89
+ }
90
+ }, [ referrer ] );
91
+
92
+ return <Page { ...config } />;
93
+ }
94
+
95
+ Layout.propTypes = {
96
+ type: PropTypes.oneOf( [ 'import', 'export' ] ),
97
+ headerButtons: PropTypes.arrayOf( PropTypes.object ),
98
+ children: PropTypes.object.isRequired,
99
+ footer: PropTypes.object,
100
+ };
101
+
102
+ Layout.defaultProps = {
103
+ headerButtons: [],
104
+ };
app/modules/import-export/assets/js/ui/loader/loader.js ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import Icon from 'elementor-app/ui/atoms/icon';
4
+
5
+ import './loader.scss';
6
+
7
+ export default function Loader( { absoluteCenter } ) {
8
+ const baseClassName = 'e-app-import-export-loader',
9
+ classes = [ baseClassName, 'eicon-loading eicon-animation-spin' ];
10
+
11
+ if ( absoluteCenter ) {
12
+ classes.push( baseClassName + '--absolute-center' );
13
+ }
14
+
15
+ return <Icon className={ arrayToClassName( classes ) } />;
16
+ }
17
+
18
+ Loader.propTypes = {
19
+ absoluteCenter: PropTypes.bool,
20
+ };
21
+
22
+ Loader.defaultProps = {
23
+ absoluteCenter: false,
24
+ };
app/modules/import-export/assets/js/ui/loader/loader.scss ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-import-export-loader-color: tints(400);
2
+ $e-app-import-export-loader-dark-color: dark-tints(400);
3
+
4
+ $root: e-app-import-export-loader;
5
+
6
+ .#{$root} {
7
+ --e-app-import-export-loader-color: #{$e-app-import-export-loader-color};
8
+
9
+ color: var(--e-app-import-export-loader-color);
10
+ font-size: 50px;
11
+
12
+ &.eicon-loading {
13
+ font-size: type( size, '30' );
14
+ }
15
+
16
+ &--absolute-center {
17
+ position: absolute;
18
+ top: 50%;
19
+ left: 50%;
20
+ transform: translateX(-50%) translateY(-50%);
21
+ }
22
+ }
23
+
24
+ .eps-theme-dark {
25
+ .#{$root} {
26
+ --e-app-import-export-loader-color: #{$e-app-import-export-loader-dark-color};
27
+ }
28
+ }
app/modules/import-export/assets/js/ui/message-banner/message-banner.js ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Heading from 'elementor-app/ui/atoms/heading';
2
+ import Text from 'elementor-app/ui/atoms/text';
3
+ import Box from 'elementor-app/ui/atoms/box';
4
+ import Grid from 'elementor-app/ui/grid/grid';
5
+
6
+ import './message-banner.scss';
7
+
8
+ export default function MessageBanner( { heading, description, button } ) {
9
+ const getDescriptionContent = () => {
10
+ if ( Array.isArray( description ) ) {
11
+ return description.join( <br /> );
12
+ }
13
+
14
+ return description;
15
+ };
16
+ return (
17
+ <Box className="e-app-import-export-message-banner" padding="20">
18
+ <Grid container alignItems="center" justify="space-between">
19
+ <Grid item>
20
+ {
21
+ heading &&
22
+ <Heading className="e-app-import-export-message-banner__heading" variant="h3" tag="h3">
23
+ { heading }
24
+ </Heading>
25
+ }
26
+
27
+ {
28
+ description &&
29
+ <Text className="e-app-import-export-message-banner__description">
30
+ { getDescriptionContent() }
31
+ </Text>
32
+ }
33
+ </Grid>
34
+
35
+ {
36
+ button &&
37
+ <Grid item>
38
+ { button }
39
+ </Grid>
40
+ }
41
+ </Grid>
42
+ </Box>
43
+ );
44
+ }
45
+
46
+ MessageBanner.propTypes = {
47
+ heading: PropTypes.string,
48
+ description: PropTypes.oneOfType( [ PropTypes.string, PropTypes.array ] ),
49
+ button: PropTypes.object,
50
+ };
app/modules/import-export/assets/js/ui/message-banner/message-banner.scss ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-import-export-message-banner-heading-color: tints(700);
2
+ $e-app-import-export-message-banner-heading-dark-color: dark-tints(300);
3
+ $e-app-import-export-message-banner-description-color: tints(600);
4
+ $e-app-import-export-message-banner-description-dark-color: dark-tints(200);
5
+
6
+ $root: e-app-import-export-message-banner;
7
+
8
+ .#{$root} {
9
+ --e-app-import-export-message-banner-heading-color: #{$e-app-import-export-message-banner-heading-color};
10
+ --e-app-import-export-message-banner-description-color: #{$e-app-import-export-message-banner-description-color};
11
+
12
+ margin-bottom: spacing(30);
13
+
14
+ &__heading {
15
+ color: var(--e-app-import-export-message-banner-heading-color);
16
+ margin-bottom: spacing(10);
17
+ }
18
+
19
+ &__description {
20
+ color: var(--e-app-import-export-message-banner-description-color);
21
+ margin-bottom :0;
22
+ }
23
+ }
24
+
25
+ .eps-theme-dark {
26
+ .#{$root} {
27
+ --e-app-import-export-message-banner-heading-color: #{$e-app-import-export-message-banner-heading-dark-color};
28
+ --e-app-import-export-message-banner-description-color: #{$e-app-import-export-message-banner-description-dark-color};
29
+ }
30
+ }
app/modules/import-export/assets/js/ui/page-header/page-header.js ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import Grid from 'elementor-app/ui/grid/grid';
4
+ import Heading from 'elementor-app/ui/atoms/heading';
5
+ import Text from 'elementor-app/ui/atoms/text';
6
+
7
+ import './page-header.scss';
8
+
9
+ // Page header.
10
+ export default function PageHeader( props ) {
11
+ const baseClassName = 'e-app-import-export-page-header',
12
+ classes = [ baseClassName, props.className ];
13
+
14
+ const handleMultiLine = ( content ) => {
15
+ if ( Array.isArray( content ) ) {
16
+ const multiLineArray = [];
17
+
18
+ content.forEach( ( line, index ) => {
19
+ if ( index ) {
20
+ multiLineArray.push( <br key={ index } /> );
21
+ }
22
+
23
+ multiLineArray.push( line );
24
+ } );
25
+
26
+ return multiLineArray;
27
+ }
28
+
29
+ return content;
30
+ };
31
+
32
+ return (
33
+ <div className={ arrayToClassName( classes ) }>
34
+ <Grid container>
35
+ <Grid item className="e-app-import-export-page-header__content-wrapper">
36
+ { props.heading && <Heading variant="display-3" className="e-app-import-export-page-header__heading">{ props.heading }</Heading> }
37
+ { props.description && <Text className="e-app-import-export-page-header__description">{ handleMultiLine( props.description ) }</Text> }
38
+ </Grid>
39
+ </Grid>
40
+ </div>
41
+ );
42
+ }
43
+
44
+ PageHeader.propTypes = {
45
+ className: PropTypes.string,
46
+ heading: PropTypes.string,
47
+ description: PropTypes.oneOfType( [
48
+ PropTypes.string,
49
+ PropTypes.array,
50
+ PropTypes.object,
51
+ ] ),
52
+ };
53
+
54
+ PageHeader.defaultProps = {
55
+ className: '',
56
+ };
app/modules/import-export/assets/js/ui/page-header/page-header.scss ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-import-export-page-header-border-bottom-color: tints(300);
2
+ $e-app-import-export-page-header-border-bottom-dark-color: dark-tints(500);
3
+ $e-app-import-export-page-header-heading-color: tints(600);
4
+ $e-app-import-export-page-header-heading-dark-color: dark-tints(100);
5
+ $e-app-import-export-page-header-description-color: tints(500);
6
+ $e-app-import-export-page-header-description-dark-color: dark-tints(100);
7
+
8
+ $root: e-app-import-export-page-header;
9
+
10
+ .#{$root} {
11
+ --e-app-import-export-page-header-border-bottom-color: #{$e-app-import-export-page-header-border-bottom-color};
12
+ --e-app-import-export-page-header-heading-color: #{$e-app-import-export-page-header-heading-color};
13
+ --e-app-import-export-page-header-description-color: #{$e-app-import-export-page-header-description-color};
14
+
15
+ border-bottom: 1px solid var(--e-app-import-export-page-header-border-bottom-color);
16
+ margin-bottom: spacing(44);
17
+
18
+ &__content-wrapper {
19
+ max-width: 645px;
20
+ }
21
+
22
+ &__heading {
23
+ color: var(--e-app-import-export-page-header-heading-color);
24
+ }
25
+
26
+ &__description {
27
+ color: var(--e-app-import-export-page-header-description-color);
28
+ margin-top: spacing(20);
29
+ }
30
+
31
+ }
32
+
33
+ .eps-theme-dark {
34
+ .#{$root} {
35
+ --e-app-import-export-page-header-border-bottom-color: #{$e-app-import-export-page-header-border-bottom-dark-color};
36
+ --e-app-import-export-page-header-heading-color: #{$e-app-import-export-page-header-heading-dark-color};
37
+ --e-app-import-export-page-header-description-color: #{$e-app-import-export-page-header-description-dark-color};
38
+ }
39
+ }
app/modules/import-export/assets/js/ui/wizard-step/wizard-step.js ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { arrayToClassName } from 'elementor-app/utils/utils.js';
2
+
3
+ import Grid from 'elementor-app/ui/grid/grid';
4
+ import Icon from 'elementor-app/ui/atoms/icon';
5
+ import Heading from 'elementor-app/ui/atoms/heading';
6
+ import Text from 'elementor-app/ui/atoms/text';
7
+
8
+ import './wizard-step.scss';
9
+
10
+ export default function WizardStep( props ) {
11
+ const baseClassName = 'e-app-import-export-wizard-step',
12
+ classes = [ baseClassName, props.className ];
13
+
14
+ return (
15
+ <Grid className={ arrayToClassName( classes ) } justify="center" container>
16
+ <Grid item>
17
+ { ( props.image || props.icon ) &&
18
+ <Grid className="e-app-import-export-wizard-step__media-container" justify="center" alignItems="end" container>
19
+ {
20
+ props.image &&
21
+ // eslint-disable-next-line jsx-a11y/alt-text
22
+ <img
23
+ className="e-app-import-export-wizard-step__image"
24
+ src={ props.image }
25
+ />
26
+ }
27
+ {
28
+ props.icon &&
29
+ <Icon className={ `e-app-import-export-wizard-step__icon ${ props.icon }` } />
30
+ }
31
+ </Grid>
32
+ }
33
+
34
+ { props.heading &&
35
+ <Heading variant="display-3" className="e-app-import-export-wizard-step__heading">
36
+ { props.heading }
37
+ </Heading>
38
+ }
39
+
40
+ { props.description &&
41
+ <Text variant="xl" className="e-app-import-export-wizard-step__description" >
42
+ { props.description }
43
+ </Text>
44
+ }
45
+
46
+ { props.info &&
47
+ <Text variant="xl" className="e-app-import-export-wizard-step__info" >
48
+ { props.info }
49
+ </Text>
50
+ }
51
+
52
+ { props.children &&
53
+ <Grid item className="e-app-import-export-wizard-step__content">
54
+ { props.children }
55
+ </Grid>
56
+ }
57
+
58
+ { props.notice &&
59
+ <Text variant="xs" className="e-app-import-export-wizard-step__notice">
60
+ { props.notice }
61
+ </Text>
62
+ }
63
+ </Grid>
64
+ </Grid>
65
+ );
66
+ }
67
+
68
+ WizardStep.propTypes = {
69
+ className: PropTypes.string,
70
+ image: PropTypes.string,
71
+ icon: PropTypes.string,
72
+ heading: PropTypes.string,
73
+ description: PropTypes.oneOfType( [ PropTypes.string, PropTypes.object ] ),
74
+ info: PropTypes.oneOfType( [ PropTypes.string, PropTypes.object ] ),
75
+ notice: PropTypes.oneOfType( [ PropTypes.string, PropTypes.object ] ),
76
+ children: PropTypes.any,
77
+ };
78
+
79
+ WizardStep.defaultProps = {
80
+ className: '',
81
+ };
app/modules/import-export/assets/js/ui/wizard-step/wizard-step.scss ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-app-import-export-wizard-step-icon-color: tints(400);
2
+ $e-app-import-export-wizard-step-icon-dark-color: dark-tints(400);
3
+ $e-app-import-export-wizard-step-text-color: tints(500);
4
+ $e-app-import-export-wizard-step-text-dark-color: dark-tints(200);
5
+ $e-app-import-export-wizard-step-bottom-text-color: tints(500);
6
+ $e-app-import-export-wizard-step-bottom-text-dark-color: dark-tints(200);
7
+
8
+ $root: e-app-import-export-wizard-step;
9
+
10
+ .#{$root} {
11
+ --e-app-import-export-wizard-step-icon-color: #{$e-app-import-export-wizard-step-icon-color};
12
+ --e-app-import-export-wizard-step-text-color: #{$e-app-import-export-wizard-step-text-color};
13
+ --e-app-import-export-wizard-step-bottom-text-color: #{$e-app-import-export-wizard-step-bottom-text-color};
14
+
15
+ height: 100%;
16
+ position: relative;
17
+ text-align: center;
18
+
19
+ &__media-container {
20
+ height: 140px;
21
+ margin: (spacing(44) * 2) 0 spacing(44);
22
+ }
23
+
24
+ &__icon {
25
+ color: var(--e-app-import-export-wizard-step-icon-color);
26
+ font-size: 50px;
27
+
28
+ &.eicon-loading {
29
+ font-size: type( size, '30' );
30
+ }
31
+ }
32
+
33
+ &__heading {
34
+ margin-bottom: spacing(24);
35
+ }
36
+
37
+ &__description,
38
+ &__info {
39
+ color: var(--e-app-import-export-wizard-step-text-color);
40
+ }
41
+
42
+ &__info {
43
+ margin-top: spacing(24);
44
+ }
45
+
46
+ &__content {
47
+ text-align: initial;
48
+ margin-bottom: spacing(20);
49
+ }
50
+
51
+ &__notice {
52
+ display: block;
53
+ position: sticky;
54
+ top: 100%; /* Will prevent text overlapping when window height is too short. */
55
+ color: var(--e-app-import-export-wizard-step-bottom-text-color);
56
+ font-style: italic;
57
+ margin-bottom: 0;
58
+ }
59
+ }
60
+
61
+ .eps-theme-dark {
62
+ .#{$root} {
63
+ --e-app-import-export-wizard-step-icon-color: #{$e-app-import-export-wizard-step-icon-dark-color};
64
+ --e-app-import-export-wizard-step-text-color: #{$e-app-import-export-wizard-step-text-dark-color};
65
+ --e-app-import-export-wizard-step-bottom-text-color: #{$e-app-import-export-wizard-step-bottom-text-dark-color};
66
+ }
67
+ }
app/modules/import-export/compatibility/base-adapter.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Compatibility;
4
+
5
+ use Elementor\App\Modules\ImportExport\Import;
6
+ use Elementor\Core\Base\Base_Object;
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit; // Exit if accessed directly
10
+ }
11
+
12
+ abstract class Base_Adapter {
13
+
14
+ /**
15
+ * @param array $manifest_data
16
+ * @param array $meta
17
+ * @return false
18
+ */
19
+ public static function is_compatibility_needed( array $manifest_data, array $meta ) {
20
+ return false;
21
+ }
22
+
23
+ public function adapt_manifest( array $manifest_data ) {
24
+ return $manifest_data;
25
+ }
26
+
27
+ public function adapt_site_settings( array $site_settings, array $manifest_data, $path ) {
28
+ return $site_settings;
29
+ }
30
+
31
+ public function adapt_template( array $template_data, array $template_settings ) {
32
+ return $template_data;
33
+ }
34
+ }
{core/app → app}/modules/import-export/compatibility/envato.php RENAMED
@@ -1,7 +1,8 @@
1
  <?php
2
 
3
- namespace Elementor\Core\App\Modules\ImportExport\Compatibility;
4
 
 
5
  use Elementor\Plugin;
6
 
7
  if ( ! defined( 'ABSPATH' ) ) {
@@ -9,33 +10,27 @@ if ( ! defined( 'ABSPATH' ) ) {
9
  }
10
 
11
  class Envato extends Base_Adapter {
12
- public static function is_compatibility_needed( array $manifest_data, array $import_settings ) {
13
  return ! empty( $manifest_data['manifest_version'] );
14
  }
15
 
16
- public function get_manifest_data( array $manifest_data ) {
17
  $templates = $manifest_data['templates'];
18
 
19
  $manifest_data['templates'] = [];
20
 
21
  foreach ( $templates as $template ) {
22
- // Envato store their global kit styles as a 'global.json' template file, this needs to be converted to 'site-settings.json' file
23
- if ( ! empty( $template['metadata']['template_type'] ) && 'global-styles' === $template['metadata']['template_type'] ) {
24
- $global_file_data = $this->importer->read_json_file( str_replace( '.json', '', $template['source'] ) );
25
-
26
- $site_settings = [ 'settings' => $global_file_data['page_settings'] ];
27
-
28
- $site_settings_file_destination = $this->importer->get_archive_file_full_path( 'site-settings.json' );
29
-
30
- file_put_contents( $site_settings_file_destination, wp_json_encode( $site_settings ) );
31
 
32
  // Getting the site-settings because Envato stores them in one of the posts.
33
  $kit = Plugin::$instance->kits_manager->get_active_kit();
34
-
35
  $kit_tabs = $kit->get_tabs();
36
-
37
  unset( $kit_tabs['settings-site-identity'] );
38
-
39
  $manifest_data['site-settings'] = array_keys( $kit_tabs );
40
 
41
  continue;
@@ -46,6 +41,7 @@ class Envato extends Base_Adapter {
46
 
47
  // Evanto uses for "name" instead of "title"
48
  $template['title'] = $template['name'];
 
49
  // Envato specifying an exact path to the template rather than using its "ID" as an index.
50
  // This extracts the "file name" part out of our exact source list and we treat that as an ID.
51
  $file_name_without_extension = str_replace( '.json', '', basename( $template['source'] ) );
@@ -54,10 +50,25 @@ class Envato extends Base_Adapter {
54
  $manifest_data['templates'][ $file_name_without_extension ] = $template;
55
  }
56
 
 
 
57
  return $manifest_data;
58
  }
59
 
60
- public function get_template_data( array $template_data, array $template_settings ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  if ( ! empty( $template_data['metadata']['elementor_pro_conditions'] ) ) {
62
  foreach ( $template_data['metadata']['elementor_pro_conditions'] as $condition ) {
63
  list ( $type, $name, $sub_name, $sub_id ) = array_pad( explode( '/', $condition ), 4, '' );
1
  <?php
2
 
3
+ namespace Elementor\App\Modules\ImportExport\Compatibility;
4
 
5
+ use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
6
  use Elementor\Plugin;
7
 
8
  if ( ! defined( 'ABSPATH' ) ) {
10
  }
11
 
12
  class Envato extends Base_Adapter {
13
+ public static function is_compatibility_needed( array $manifest_data, array $meta ) {
14
  return ! empty( $manifest_data['manifest_version'] );
15
  }
16
 
17
+ public function adapt_manifest( array $manifest_data ) {
18
  $templates = $manifest_data['templates'];
19
 
20
  $manifest_data['templates'] = [];
21
 
22
  foreach ( $templates as $template ) {
23
+ // Envato store their global kit styles as a 'global.json' template file.
24
+ // We need to be able to know the path to this specifc 'global.json' since it functions as the site-settings.json
25
+ $is_global = ! empty( $template['metadata']['template_type'] ) && 'global-styles' === $template['metadata']['template_type'];
26
+ if ( $is_global ) {
27
+ // Adding the path of the 'global.json' template to the manifest which will be used in the future.
28
+ $manifest_data['path-to-envto-site-settings'] = $template['source'];
 
 
 
29
 
30
  // Getting the site-settings because Envato stores them in one of the posts.
31
  $kit = Plugin::$instance->kits_manager->get_active_kit();
 
32
  $kit_tabs = $kit->get_tabs();
 
33
  unset( $kit_tabs['settings-site-identity'] );
 
34
  $manifest_data['site-settings'] = array_keys( $kit_tabs );
35
 
36
  continue;
41
 
42
  // Evanto uses for "name" instead of "title"
43
  $template['title'] = $template['name'];
44
+
45
  // Envato specifying an exact path to the template rather than using its "ID" as an index.
46
  // This extracts the "file name" part out of our exact source list and we treat that as an ID.
47
  $file_name_without_extension = str_replace( '.json', '', basename( $template['source'] ) );
50
  $manifest_data['templates'][ $file_name_without_extension ] = $template;
51
  }
52
 
53
+ $manifest_data['name'] = $manifest_data['title'];
54
+
55
  return $manifest_data;
56
  }
57
 
58
+ public function adapt_site_settings( array $site_settings, array $manifest_data, $path ) {
59
+ if ( empty( $manifest_data['path-to-envto-site-settings'] ) ) {
60
+ return $site_settings;
61
+ }
62
+
63
+ $global_file_path = $path . $manifest_data['path-to-envto-site-settings'];
64
+ $global_file_data = ImportExportUtils::read_json_file( $global_file_path );
65
+
66
+ return [
67
+ 'settings' => $global_file_data['page_settings'],
68
+ ];
69
+ }
70
+
71
+ public function adapt_template( array $template_data, array $template_settings ) {
72
  if ( ! empty( $template_data['metadata']['elementor_pro_conditions'] ) ) {
73
  foreach ( $template_data['metadata']['elementor_pro_conditions'] as $condition ) {
74
  list ( $type, $name, $sub_name, $sub_id ) = array_pad( explode( '/', $condition ), 4, '' );
{core/app → app}/modules/import-export/compatibility/kit-library.php RENAMED
@@ -1,17 +1,17 @@
1
  <?php
2
 
3
- namespace Elementor\Core\App\Modules\ImportExport\Compatibility;
4
 
5
  if ( ! defined( 'ABSPATH' ) ) {
6
  exit; // Exit if accessed directly
7
  }
8
 
9
  class Kit_Library extends Base_Adapter {
10
- public static function is_compatibility_needed( array $manifest_data, array $import_settings ) {
11
- return ! empty( $import_settings['referrer'] ) && 'kit-library' === $import_settings['referrer'];
12
  }
13
 
14
- public function get_manifest_data( array $manifest_data ) {
15
  if ( ! empty( $manifest_data['content']['page'] ) ) {
16
  foreach ( $manifest_data['content']['page'] as & $page ) {
17
  $page['thumbnail'] = false;
1
  <?php
2
 
3
+ namespace Elementor\App\Modules\ImportExport\Compatibility;
4
 
5
  if ( ! defined( 'ABSPATH' ) ) {
6
  exit; // Exit if accessed directly
7
  }
8
 
9
  class Kit_Library extends Base_Adapter {
10
+ public static function is_compatibility_needed( array $manifest_data, array $meta ) {
11
+ return ! empty( $meta['referrer'] ) && 'kit-library' === $meta['referrer'];
12
  }
13
 
14
+ public function adapt_manifest( array $manifest_data ) {
15
  if ( ! empty( $manifest_data['content']['page'] ) ) {
16
  foreach ( $manifest_data['content']['page'] as & $page ) {
17
  $page['thumbnail'] = false;
app/modules/import-export/module.php ADDED
@@ -0,0 +1,680 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Elementor\App\Modules\ImportExport;
3
+
4
+ use Elementor\App\Modules\ImportExport\Processes\Export;
5
+ use Elementor\App\Modules\ImportExport\Processes\Import;
6
+ use Elementor\App\Modules\ImportExport\Processes\Revert;
7
+ use Elementor\Core\Base\Module as BaseModule;
8
+ use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
9
+ use Elementor\Core\Files\Uploads_Manager;
10
+ use Elementor\Modules\System_Info\Reporters\Server;
11
+ use Elementor\Plugin;
12
+ use Elementor\Tools;
13
+ use Elementor\Utils as ElementorUtils;
14
+ use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
15
+
16
+ if ( ! defined( 'ABSPATH' ) ) {
17
+ exit; // Exit if accessed directly
18
+ }
19
+
20
+ /**
21
+ * Import Export Module
22
+ *
23
+ * Responsible for initializing Elementor App functionality
24
+ */
25
+ class Module extends BaseModule {
26
+ const FORMAT_VERSION = '2.0';
27
+
28
+ const EXPORT_TRIGGER_KEY = 'elementor_export_kit';
29
+
30
+ const UPLOAD_TRIGGER_KEY = 'elementor_upload_kit';
31
+
32
+ const IMPORT_TRIGGER_KEY = 'elementor_import_kit';
33
+
34
+ const REFERRER_KIT_LIBRARY = 'kit-library';
35
+
36
+ const REFERRER_LOCAL = 'local';
37
+
38
+ const PERMISSIONS_ERROR_KEY = 'plugin-installation-permissions-error';
39
+
40
+ const NO_WRITE_PERMISSIONS_KEY = 'no-write-permissions';
41
+
42
+ const OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS = 'elementor_import_sessions';
43
+
44
+ const OPTION_KEY_ELEMENTOR_REVERT_SESSIONS = 'elementor_revert_sessions';
45
+
46
+ const META_KEY_ELEMENTOR_IMPORT_SESSION_ID = '_elementor_import_session_id';
47
+
48
+ const META_KEY_ELEMENTOR_EDIT_MODE = '_elementor_edit_mode';
49
+
50
+ /**
51
+ * Assigning the export process to a property, so we can use the process from outside the class.
52
+ *
53
+ * @var Export
54
+ */
55
+ public $export;
56
+
57
+ /**
58
+ * Assigning the import process to a property, so we can use the process from outside the class.
59
+ *
60
+ * @var Import
61
+ */
62
+ public $import;
63
+
64
+ /**
65
+ * Assigning the revert process to a property, so we can use the process from outside the class.
66
+ *
67
+ * @var Revert
68
+ */
69
+ public $revert;
70
+
71
+ /**
72
+ * Get name.
73
+ *
74
+ * @access public
75
+ *
76
+ * @return string
77
+ */
78
+ public function get_name() {
79
+ return 'import-export';
80
+ }
81
+
82
+ public function __construct() {
83
+ $this->register_actions();
84
+
85
+ if ( ElementorUtils::is_wp_cli() ) {
86
+ \WP_CLI::add_command( 'elementor kit', WP_CLI::class );
87
+ }
88
+
89
+ ( new Usage() )->register();
90
+ }
91
+
92
+ public function get_init_settings() {
93
+ if ( ! Plugin::$instance->app->is_current() ) {
94
+ return [];
95
+ }
96
+
97
+ return $this->get_config_data();
98
+ }
99
+
100
+ /**
101
+ * Register the import/export tab in elementor tools.
102
+ */
103
+ public function register_settings_tab( Tools $tools ) {
104
+ $tools->add_tab( 'import-export-kit', [
105
+ 'label' => esc_html__( 'Import / Export Kit', 'elementor' ),
106
+ 'sections' => [
107
+ 'intro' => [
108
+ 'label' => esc_html__( 'Template Kits', 'elementor' ),
109
+ 'callback' => function() {
110
+ $this->render_import_export_tab_content();
111
+ },
112
+ 'fields' => [],
113
+ ],
114
+ ],
115
+ ] );
116
+ }
117
+
118
+ /**
119
+ * Render the import/export tab content.
120
+ */
121
+ private function render_import_export_tab_content() {
122
+ $intro_text_link = sprintf( '<a href="https://go.elementor.com/wp-dash-import-export-general/" target="_blank">%s</a>', esc_html__( 'Learn more', 'elementor' ) );
123
+
124
+ $intro_text = sprintf(
125
+ /* translators: 1: New line break, 2: Learn More link. */
126
+ __( 'Design sites faster with a template kit that contains some or all components of a complete site, like templates, content & site settings.%1$sYou can import a kit and apply it to your site, or export the elements from this site to be used anywhere else. %2$s', 'elementor' ),
127
+ '<br>',
128
+ $intro_text_link
129
+ );
130
+
131
+ $content_data = [
132
+ 'export' => [
133
+ 'title' => esc_html__( 'Export a Template Kit', 'elementor' ),
134
+ 'button' => [
135
+ 'url' => Plugin::$instance->app->get_base_url() . '#/export',
136
+ 'text' => esc_html__( 'Start Export', 'elementor' ),
137
+ ],
138
+ 'description' => esc_html__( 'Bundle your whole site - or just some of its elements - to be used for another website.', 'elementor' ),
139
+ 'link' => [
140
+ 'url' => 'https://go.elementor.com/wp-dash-import-export-export-flow/',
141
+ 'text' => esc_html__( 'Learn More', 'elementor' ),
142
+ ],
143
+ ],
144
+ 'import' => [
145
+ 'title' => esc_html__( 'Import a Template Kit', 'elementor' ),
146
+ 'button' => [
147
+ 'url' => Plugin::$instance->app->get_base_url() . '#/import',
148
+ 'text' => esc_html__( 'Start Import', 'elementor' ),
149
+ ],
150
+ 'description' => esc_html__( 'Apply the design and settings of another site to this one.', 'elementor' ),
151
+ 'link' => [
152
+ 'url' => 'https://go.elementor.com/wp-dash-import-export-import-flow/',
153
+ 'text' => esc_html__( 'Learn More', 'elementor' ),
154
+ ],
155
+ ],
156
+ ];
157
+
158
+ $this->revert = new Revert();
159
+ $last_imported_kit = $this->revert->get_last_import_session();
160
+ $penultimate_imported_kit = $this->revert->get_penultimate_import_session();
161
+
162
+ $user_date_format = get_option( 'date_format' );
163
+ $user_time_format = get_option( 'time_format' );
164
+ $date_format = $user_date_format . ' ' . $user_time_format;
165
+
166
+ $should_show_revert_section = $this->should_show_revert_section( $last_imported_kit );
167
+
168
+ if ( $should_show_revert_section ) {
169
+ if ( ! empty( $penultimate_imported_kit ) ) {
170
+ $revert_text = sprintf(
171
+ esc_html__( 'Remove all the content and site settings that came with "%1$s" on %2$s %3$s and revert to the site setting that came with "%4$s" on %5$s.', 'elementor' ),
172
+ ! empty( $last_imported_kit['kit_name'] ) ? $last_imported_kit['kit_name'] : esc_html__( 'imported kit', 'elementor' ),
173
+ gmdate( $date_format, $last_imported_kit['start_timestamp'] ),
174
+ '<br>',
175
+ ! empty( $penultimate_imported_kit['kit_name'] ) ? $penultimate_imported_kit['kit_name'] : esc_html__( 'imported kit', 'elementor' ),
176
+ gmdate( $date_format, $penultimate_imported_kit['start_timestamp'] )
177
+ );
178
+ } else {
179
+ $revert_text = sprintf(
180
+ esc_html__( 'Remove all the content and site settings that came with "%1$s" on %2$s.%3$s Your original site settings will be restored.', 'elementor' ),
181
+ ! empty( $last_imported_kit['kit_name'] ) ? $last_imported_kit['kit_name'] : esc_html__( 'imported kit', 'elementor' ),
182
+ gmdate( $date_format, $last_imported_kit['start_timestamp'] ),
183
+ '<br>'
184
+ );
185
+ }
186
+ }
187
+ ?>
188
+
189
+ <div class="tab-import-export-kit__content">
190
+ <p class="tab-import-export-kit__info"><?php ElementorUtils::print_unescaped_internal_string( $intro_text ); ?></p>
191
+
192
+ <div class="tab-import-export-kit__wrapper">
193
+ <?php foreach ( $content_data as $data ) { ?>
194
+ <div class="tab-import-export-kit__container">
195
+ <div class="tab-import-export-kit__box">
196
+ <h2><?php ElementorUtils::print_unescaped_internal_string( $data['title'] ); ?></h2>
197
+ <a href="<?php ElementorUtils::print_unescaped_internal_string( $data['button']['url'] ); ?>" class="elementor-button elementor-button-success">
198
+ <?php ElementorUtils::print_unescaped_internal_string( $data['button']['text'] ); ?>
199
+ </a>
200
+ </div>
201
+ <p><?php ElementorUtils::print_unescaped_internal_string( $data['description'] ); ?></p>
202
+ <a href="<?php ElementorUtils::print_unescaped_internal_string( $data['link']['url'] ); ?>" target="_blank"><?php ElementorUtils::print_unescaped_internal_string( $data['link']['text'] ); ?></a>
203
+ </div>
204
+ <?php } ?>
205
+ </div>
206
+
207
+ <?php
208
+ if ( $should_show_revert_section ) {
209
+ $link_attributes = [
210
+ 'href' => wp_nonce_url( admin_url( 'admin-post.php?action=elementor_revert_kit' ), 'elementor_revert_kit' ),
211
+ 'id' => 'elementor-import-export__revert_kit',
212
+ 'class' => 'button',
213
+ ];
214
+ ?>
215
+ <div class="tab-import-export-kit__revert">
216
+ <h2>
217
+ <?php ElementorUtils::print_unescaped_internal_string( esc_html__( 'Remove the most recent Kit', 'elementor' ) ); ?>
218
+ </h2>
219
+ <p class="tab-import-export-kit__info">
220
+ <?php ElementorUtils::print_unescaped_internal_string( $revert_text ); ?>
221
+ </p>
222
+ <a <?php ElementorUtils::print_html_attributes( $link_attributes ); ?> >
223
+ <?php ElementorUtils::print_unescaped_internal_string( esc_html__( 'Remove Kit', 'elementor' ) ); ?>
224
+ </a>
225
+ </div>
226
+ <?php } ?>
227
+ </div>
228
+ <?php
229
+ }
230
+
231
+ /**
232
+ * Upload a kit zip file and get the kit data.
233
+ *
234
+ * Assigning the Import process to the 'import' property,
235
+ * so it will be available to use in different places such as: WP_Cli, Pro, etc.
236
+ *
237
+ * @param string $file Path to the file.
238
+ * @param string $referrer Referrer of the file 'local' or 'kit-library'.
239
+ * @return array
240
+ * @throws \Exception
241
+ */
242
+ public function upload_kit( $file, $referrer ) {
243
+ $this->ensure_writing_permissions();
244
+
245
+ $this->import = new Import( $file, [ 'referrer' => $referrer ] );
246
+
247
+ return [
248
+ 'session' => $this->import->get_session_id(),
249
+ 'manifest' => $this->import->get_manifest(),
250
+ 'conflicts' => $this->import->get_settings_conflicts(),
251
+ ];
252
+ }
253
+
254
+ /**
255
+ * Import a kit by session_id.
256
+ * Upload and import a kit by kit zip file.
257
+ *
258
+ * Assigning the Import process to the 'import' property,
259
+ * so it will be available to use in different places such as: WP_Cli, Pro, etc.
260
+ *
261
+ * @param string $path Path to the file or session_id.
262
+ * @param array $settings Settings the import use to determine which content to import.
263
+ * (e.g: include, selected_plugins, selected_cpt, selected_override_conditions, etc.)
264
+ * @return array
265
+ * @throws \Exception
266
+ */
267
+ public function import_kit( $path, $settings ) {
268
+ $this->ensure_writing_permissions();
269
+
270
+ $this->import = new Import( $path, $settings );
271
+ $this->import->register_default_runners();
272
+
273
+ do_action( 'elementor/import-export/import-kit', $this->import );
274
+
275
+ return $this->import->run();
276
+ }
277
+
278
+ /**
279
+ * Export a kit.
280
+ *
281
+ * Assigning the Export process to the 'export' property,
282
+ * so it will be available to use in different places such as: WP_Cli, Pro, etc.
283
+ *
284
+ * @param array $settings Settings the export use to determine which content to export.
285
+ * (e.g: include, kit_info, selected_plugins, selected_cpt, etc.)
286
+ * @return array
287
+ * @throws \Exception
288
+ */
289
+ public function export_kit( $settings ) {
290
+ $this->ensure_writing_permissions();
291
+
292
+ $this->export = new Export( $settings );
293
+ $this->export->register_default_runners();
294
+
295
+ do_action( 'elementor/import-export/export-kit', $this->export );
296
+
297
+ return $this->export->run();
298
+ }
299
+
300
+ /**
301
+ * Handle revert kit ajax request.
302
+ */
303
+ public function revert_last_imported_kit() {
304
+ $this->revert = new Revert();
305
+ $this->revert->register_default_runners();
306
+
307
+ do_action( 'elementor/import-export/revert-kit', $this->revert );
308
+
309
+ $this->revert->run();
310
+ }
311
+
312
+
313
+ /**
314
+ * Handle revert last imported kit ajax request.
315
+ */
316
+ public function handle_revert_last_imported_kit() {
317
+ check_admin_referer( 'elementor_revert_kit' );
318
+
319
+ $this->revert_last_imported_kit();
320
+
321
+ wp_safe_redirect( admin_url( 'admin.php?page=' . Tools::PAGE_ID . '#tab-import-export-kit' ) );
322
+ die;
323
+ }
324
+
325
+ /**
326
+ * Register appropriate actions.
327
+ */
328
+ private function register_actions() {
329
+ add_action( 'admin_init', function() {
330
+ if ( wp_doing_ajax() &&
331
+ isset( $_POST['action'] ) &&
332
+ isset( $_POST['_nonce'] ) &&
333
+ wp_verify_nonce( $_POST['_nonce'], Ajax::NONCE_KEY ) &&
334
+ current_user_can( 'manage_options' )
335
+ ) {
336
+ $this->maybe_handle_ajax();
337
+ }
338
+ } );
339
+
340
+ add_action( 'admin_post_elementor_revert_kit', [ $this, 'handle_revert_last_imported_kit' ] );
341
+
342
+ add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
343
+
344
+ $page_id = Tools::PAGE_ID;
345
+
346
+ add_action( "elementor/admin/after_create_settings/{$page_id}", [ $this, 'register_settings_tab' ] );
347
+ }
348
+
349
+ private function ensure_writing_permissions() {
350
+ $server = new Server();
351
+
352
+ $paths_to_check = [
353
+ Server::KEY_PATH_WP_CONTENT_DIR => $server->get_system_path( Server::KEY_PATH_WP_CONTENT_DIR ),
354
+ Server::KEY_PATH_UPLOADS_DIR => $server->get_system_path( Server::KEY_PATH_UPLOADS_DIR ),
355
+ Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR => $server->get_system_path( Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR ),
356
+ ];
357
+
358
+ $permissions = $server->get_paths_permissions( $paths_to_check );
359
+
360
+ // WP Content dir has to be exists and writable.
361
+ if ( ! $permissions[ Server::KEY_PATH_WP_CONTENT_DIR ]['write'] ) {
362
+ throw new \Error( self::NO_WRITE_PERMISSIONS_KEY . 'in - ' . Server::KEY_PATH_WP_CONTENT_DIR );
363
+ }
364
+
365
+ // WP Uploads dir has to be exists and writable.
366
+ if ( ! $permissions[ Server::KEY_PATH_UPLOADS_DIR ]['write'] ) {
367
+ throw new \Error( self::NO_WRITE_PERMISSIONS_KEY . 'in - ' . Server::KEY_PATH_UPLOADS_DIR );
368
+ }
369
+
370
+ // Elementor uploads dir permissions is divided to 2 cases:
371
+ // 1. If the dir exists, it has to be writable.
372
+ // 2. If the dir doesn't exist, the parent dir has to be writable (wp uploads dir), so we can create it.
373
+ if ( $permissions[ Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR ]['exists'] && ! $permissions[ Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR ]['write'] ) {
374
+ throw new \Error( self::NO_WRITE_PERMISSIONS_KEY . 'in - ' . Server::KEY_PATH_ELEMENTOR_UPLOADS_DIR );
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Enqueue admin scripts
380
+ */
381
+ public function enqueue_scripts() {
382
+ wp_enqueue_script(
383
+ 'elementor-import-export-admin',
384
+ $this->get_js_assets_url( 'import-export-admin' ),
385
+ [ 'elementor-common' ],
386
+ ELEMENTOR_VERSION,
387
+ true
388
+ );
389
+ }
390
+
391
+ /**
392
+ * Assign each ajax action to a method.
393
+ */
394
+ private function maybe_handle_ajax() {
395
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
396
+ switch ( $_POST['action'] ) {
397
+ case static::EXPORT_TRIGGER_KEY:
398
+ $this->handle_export_kit();
399
+ break;
400
+
401
+ case static::UPLOAD_TRIGGER_KEY:
402
+ $this->handle_upload_kit();
403
+ break;
404
+
405
+ case static::IMPORT_TRIGGER_KEY:
406
+ $this->handle_import_kit();
407
+ break;
408
+
409
+ default:
410
+ break;
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Handle upload kit ajax request.
416
+ */
417
+ private function handle_upload_kit() {
418
+ // PHPCS - Already validated in caller function.
419
+ if ( ! empty( $_POST['e_import_file'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
420
+ if (
421
+ ! isset( $_POST['e_kit_library_nonce'] ) ||
422
+ ! wp_verify_nonce( $_POST['e_kit_library_nonce'], 'kit-library-import' )
423
+ ) {
424
+ throw new \Error( esc_html__( 'Invalid kit library nonce', 'elementor' ) );
425
+ }
426
+
427
+ $file_url = $_POST['e_import_file'];
428
+
429
+ if ( ! filter_var( $file_url, FILTER_VALIDATE_URL ) || 0 !== strpos( $file_url, 'http' ) ) {
430
+ throw new \Error( esc_html__( 'Invalid URL', 'elementor' ) );
431
+ }
432
+
433
+ $remote_zip_request = wp_remote_get( $file_url );
434
+
435
+ if ( is_wp_error( $remote_zip_request ) ) {
436
+ throw new \Error( $remote_zip_request->get_error_message() );
437
+ }
438
+
439
+ if ( 200 !== $remote_zip_request['response']['code'] ) {
440
+ throw new \Error( $remote_zip_request['response']['message'] );
441
+ }
442
+
443
+ $file_name = Plugin::$instance->uploads_manager->create_temp_file( $remote_zip_request['body'], 'kit.zip' );
444
+ $referrer = static::REFERRER_KIT_LIBRARY;
445
+ } else {
446
+ // PHPCS - Already validated in caller function.
447
+ $file_name = $_FILES['e_import_file']['tmp_name']; // phpcs:ignore WordPress.Security.NonceVerification.Missing
448
+ $referrer = static::REFERRER_LOCAL;
449
+ }
450
+
451
+ $uploaded_kit = $this->upload_kit( $file_name, $referrer );
452
+ $session_dir = $uploaded_kit['session'];
453
+ $manifest = $uploaded_kit['manifest'];
454
+ $conflicts = $uploaded_kit['conflicts'];
455
+
456
+ if ( ! empty( $file_url ) ) {
457
+ Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $file_name ) );
458
+ }
459
+
460
+ if ( isset( $manifest['plugins'] ) && ! current_user_can( 'install_plugins' ) ) {
461
+ throw new \Error( static::PERMISSIONS_ERROR_KEY );
462
+ }
463
+
464
+ $result = [
465
+ 'session' => $session_dir,
466
+ 'manifest' => $manifest,
467
+ ];
468
+
469
+ if ( ! empty( $conflicts ) ) {
470
+ $result['conflicts'] = $conflicts;
471
+ } else {
472
+ // Moved into the IE process \Elementor\App\Modules\ImportExport\Processes\Import::get_default_settings_conflicts
473
+ // TODO: remove in 3.10.0
474
+ $result = apply_filters( 'elementor/import/stage_1/result', $result );
475
+ }
476
+
477
+ wp_send_json_success( $result );
478
+ }
479
+
480
+ /**
481
+ * Handle import kit ajax request.
482
+ */
483
+ private function handle_import_kit() {
484
+ // PHPCS - Already validated in caller function
485
+ $settings = json_decode( stripslashes( $_POST['data'] ), true ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
486
+ $tmp_folder_id = $settings['session'];
487
+
488
+ $import = $this->import_kit( $tmp_folder_id, $settings );
489
+
490
+ // get_settings_config() added manually because the frontend Ajax request doesn't trigger the get_init_settings().
491
+ $import['configData'] = $this->get_config_data();
492
+
493
+ wp_send_json_success( $import );
494
+ }
495
+
496
+ /**
497
+ * Handle export kit ajax request.
498
+ */
499
+ private function handle_export_kit() {
500
+ // PHPCS - Already validated in caller function
501
+ $settings = json_decode( stripslashes( $_POST['data'] ), true ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
502
+ $export = $this->export_kit( $settings );
503
+
504
+ $file_name = $export['file_name'];
505
+ $file = ElementorUtils::file_get_contents( $file_name );
506
+
507
+ if ( ! $file ) {
508
+ throw new \Error( esc_html__( 'Could not read the exported file', 'elementor' ) );
509
+ }
510
+
511
+ Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $file_name ) );
512
+
513
+ $result = [
514
+ 'manifest' => $export['manifest'],
515
+ 'file' => base64_encode( $file ),
516
+ ];
517
+
518
+ wp_send_json_success( $result );
519
+ }
520
+
521
+ /**
522
+ * Get config data that will be exposed to the frontend.
523
+ */
524
+ private function get_config_data() {
525
+ $export_nonce = wp_create_nonce( 'elementor_export' );
526
+ $export_url = add_query_arg( [ '_nonce' => $export_nonce ], Plugin::$instance->app->get_base_url() );
527
+
528
+ return [
529
+ 'exportURL' => $export_url,
530
+ 'summaryTitles' => $this->get_summary_titles(),
531
+ 'builtinWpPostTypes' => ImportExportUtils::get_builtin_wp_post_types(),
532
+ 'elementorPostTypes' => ImportExportUtils::get_elementor_post_types(),
533
+ 'isUnfilteredFilesEnabled' => Uploads_Manager::are_unfiltered_uploads_enabled(),
534
+ 'elementorHomePageUrl' => $this->get_elementor_home_page_url(),
535
+ 'recentlyEditedElementorPageUrl' => $this->get_recently_edited_elementor_page_url(),
536
+ ];
537
+ }
538
+
539
+ /**
540
+ * Get labels of Elementor document types, Elementor Post types, WordPress Post types and Custom Post types.
541
+ */
542
+ private function get_summary_titles() {
543
+ $summary_titles = [];
544
+
545
+ $document_types = Plugin::$instance->documents->get_document_types();
546
+
547
+ foreach ( $document_types as $name => $document_type ) {
548
+ $summary_titles['templates'][ $name ] = [
549
+ 'single' => $document_type::get_title(),
550
+ 'plural' => $document_type::get_plural_title(),
551
+ ];
552
+ }
553
+
554
+ $elementor_post_types = ImportExportUtils::get_elementor_post_types();
555
+ $wp_builtin_post_types = ImportExportUtils::get_builtin_wp_post_types();
556
+ $post_types = array_merge( $elementor_post_types, $wp_builtin_post_types );
557
+
558
+ foreach ( $post_types as $post_type ) {
559
+ $post_type_object = get_post_type_object( $post_type );
560
+
561
+ $summary_titles['content'][ $post_type ] = [
562
+ 'single' => $post_type_object->labels->singular_name,
563
+ 'plural' => $post_type_object->label,
564
+ ];
565
+ }
566
+
567
+ $custom_post_types = ImportExportUtils::get_registered_cpt_names();
568
+ if ( ! empty( $custom_post_types ) ) {
569
+ foreach ( $custom_post_types as $custom_post_type ) {
570
+
571
+ $custom_post_types_object = get_post_type_object( $custom_post_type );
572
+ // CPT data appears in two arrays:
573
+ // 1. content object: in order to show the export summary when completed in getLabel function
574
+ $summary_titles['content'][ $custom_post_type ] = [
575
+ 'single' => $custom_post_types_object->labels->singular_name,
576
+ 'plural' => $custom_post_types_object->label,
577
+ ];
578
+
579
+ // 2. customPostTypes object: in order to actually export the data
580
+ $summary_titles['content']['customPostTypes'][ $custom_post_type ] = [
581
+ 'single' => $custom_post_types_object->labels->singular_name,
582
+ 'plural' => $custom_post_types_object->label,
583
+ ];
584
+ }
585
+ }
586
+
587
+ $active_kit = Plugin::$instance->kits_manager->get_active_kit();
588
+
589
+ foreach ( $active_kit->get_tabs() as $key => $tab ) {
590
+ $summary_titles['site-settings'][ $key ] = $tab->get_title();
591
+ }
592
+
593
+ return $summary_titles;
594
+ }
595
+
596
+ public function should_show_revert_section( $last_imported_kit ) {
597
+ if ( empty( $last_imported_kit ) ) {
598
+ return false;
599
+ }
600
+
601
+ // TODO: BC - remove in the future
602
+ // The 'templates' runner was in core and moved to the Pro plugin. (Part of it still exits in the Core for BC)
603
+ // The runner that is in the core version is missing the revert functionality,
604
+ // therefore we shouldn't display the revert section if the import process done with the core version.
605
+ $is_import_templates_ran = isset( $last_imported_kit['runners']['templates'] );
606
+ if ( $this->has_pro() && $is_import_templates_ran ) {
607
+ $has_imported_templates = ! empty( $last_imported_kit['runners']['templates'] );
608
+
609
+ return $has_imported_templates;
610
+ }
611
+
612
+ return true;
613
+ }
614
+
615
+ public function has_pro(): bool {
616
+ return ElementorUtils::has_pro();
617
+ }
618
+
619
+ private function get_elementor_editor_home_page_url() {
620
+ if ( 'page' !== get_option( 'show_on_front' ) ) {
621
+ return '';
622
+ }
623
+
624
+ $frontpage_id = get_option( 'page_on_front' );
625
+
626
+ return $this->get_elementor_editor_page_url( $frontpage_id );
627
+ }
628
+
629
+ private function get_elementor_home_page_url() {
630
+ if ( 'page' !== get_option( 'show_on_front' ) ) {
631
+ return '';
632
+ }
633
+
634
+ $frontpage_id = get_option( 'page_on_front' );
635
+
636
+ return $this->get_elementor_page_url( $frontpage_id );
637
+ }
638
+
639
+ private function get_recently_edited_elementor_page_url() {
640
+ $query = ElementorUtils::get_recently_edited_posts_query( [ 'posts_per_page' => 1 ] );
641
+
642
+ if ( ! isset( $query->post ) ) {
643
+ return '';
644
+ }
645
+
646
+ return $this->get_elementor_page_url( $query->post->ID );
647
+ }
648
+
649
+ private function get_recently_edited_elementor_editor_page_url() {
650
+ $query = ElementorUtils::get_recently_edited_posts_query( [ 'posts_per_page' => 1 ] );
651
+
652
+ if ( ! isset( $query->post ) ) {
653
+ return '';
654
+ }
655
+
656
+ return $this->get_elementor_editor_page_url( $query->post->ID );
657
+ }
658
+
659
+ private function get_elementor_document( $page_id ) {
660
+ $document = Plugin::$instance->documents->get( $page_id );
661
+
662
+ if ( ! $document || ! $document->is_built_with_elementor() ) {
663
+ return false;
664
+ }
665
+
666
+ return $document;
667
+ }
668
+
669
+ private function get_elementor_page_url( $page_id ) {
670
+ $document = $this->get_elementor_document( $page_id );
671
+
672
+ return $document ? $document->get_preview_url() : '';
673
+ }
674
+
675
+ private function get_elementor_editor_page_url( $page_id ) {
676
+ $document = $this->get_elementor_document( $page_id );
677
+
678
+ return $document ? $document->get_edit_url() : '';
679
+ }
680
+ }
app/modules/import-export/processes/export.php ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Processes;
4
+
5
+ use Elementor\App\Modules\ImportExport\Module;
6
+ use Elementor\App\Modules\ImportExport\Utils;
7
+ use Elementor\Core\Utils\Str;
8
+ use Elementor\Plugin;
9
+
10
+ use Elementor\App\Modules\ImportExport\Runners\Export\Elementor_Content;
11
+ use Elementor\App\Modules\ImportExport\Runners\Export\Export_Runner_Base;
12
+ use Elementor\App\Modules\ImportExport\Runners\Export\Plugins;
13
+ use Elementor\App\Modules\ImportExport\Runners\Export\Site_Settings;
14
+ use Elementor\App\Modules\ImportExport\Runners\Export\Taxonomies;
15
+ use Elementor\App\Modules\ImportExport\Runners\Export\Templates;
16
+ use Elementor\App\Modules\ImportExport\Runners\Export\Wp_Content;
17
+
18
+ class Export {
19
+ const ZIP_ARCHIVE_MODULE_MISSING = 'zip-archive-module-is-missing';
20
+
21
+ /**
22
+ * @var Export_Runner_Base[]
23
+ */
24
+ protected $runners = [];
25
+
26
+ /**
27
+ * Selected content types to export.
28
+ *
29
+ * @var array
30
+ */
31
+ private $settings_include;
32
+
33
+ /**
34
+ * The kit information. (e.g: title, description)
35
+ *
36
+ * @var array $export_data
37
+ */
38
+ private $settings_kit_info;
39
+
40
+ /**
41
+ * Selected plugins to export.
42
+ * Contains the plugins essential data for export. (e.g: name, path, version, etc.)
43
+ *
44
+ * @var array
45
+ */
46
+ private $settings_selected_plugins;
47
+
48
+ /**
49
+ * Selected custom post types to export.
50
+ *
51
+ * @var array
52
+ */
53
+ private $settings_selected_custom_post_types;
54
+
55
+ /**
56
+ * The output data of the export process.
57
+ * Will be written into the manifest.json file.
58
+ *
59
+ * @var array
60
+ */
61
+ private $manifest_data;
62
+
63
+ /**
64
+ * The zip archive object.
65
+ *
66
+ * @var \ZipArchive
67
+ */
68
+ private $zip;
69
+
70
+ public function __construct( $settings = [] ) {
71
+ $this->settings_include = ! empty( $settings['include'] ) ? $settings['include'] : null;
72
+ $this->settings_kit_info = ! empty( $settings['kitInfo'] ) ? $settings['kitInfo'] : null;
73
+ $this->settings_selected_plugins = isset( $settings['plugins'] ) ? $settings['plugins'] : null;
74
+ $this->settings_selected_custom_post_types = isset( $settings['selectedCustomPostTypes'] ) ? $settings['selectedCustomPostTypes'] : null;
75
+ }
76
+
77
+ /**
78
+ * Register a runner.
79
+ *
80
+ * @param Export_Runner_Base $runner_instance
81
+ */
82
+ public function register( Export_Runner_Base $runner_instance ) {
83
+ $this->runners[ $runner_instance::get_name() ] = $runner_instance;
84
+ }
85
+
86
+ public function register_default_runners() {
87
+ $this->register( new Site_Settings() );
88
+ $this->register( new Plugins() );
89
+ $this->register( new Templates() );
90
+ $this->register( new Taxonomies() );
91
+ $this->register( new Elementor_Content() );
92
+ $this->register( new Wp_Content() );
93
+ }
94
+
95
+ /**
96
+ * Execute the export process.
97
+ *
98
+ * @return array The export data output.
99
+ * @throws \Exception
100
+ */
101
+ public function run() {
102
+ if ( empty( $this->runners ) ) {
103
+ throw new \Exception( 'Please specify export runners.' );
104
+ }
105
+
106
+ $this->set_default_settings();
107
+
108
+ $this->init_zip_archive();
109
+ $this->init_manifest_data();
110
+
111
+ $data = [
112
+ 'include' => $this->settings_include,
113
+ 'selected_plugins' => $this->settings_selected_plugins,
114
+ 'selected_custom_post_types' => $this->settings_selected_custom_post_types,
115
+ ];
116
+
117
+ foreach ( $this->runners as $runner ) {
118
+ if ( $runner->should_export( $data ) ) {
119
+ $export_result = $runner->export( $data );
120
+ $this->handle_export_result( $export_result );
121
+ }
122
+ }
123
+
124
+ $this->add_json_file( 'manifest', $this->manifest_data );
125
+
126
+ $zip_file_name = $this->zip->filename;
127
+ $this->zip->close();
128
+
129
+ return [
130
+ 'manifest' => $this->manifest_data,
131
+ 'file_name' => $zip_file_name,
132
+ ];
133
+ }
134
+
135
+ /**
136
+ * Set default settings for the export.
137
+ */
138
+ private function set_default_settings() {
139
+ if ( ! is_array( $this->get_settings_include() ) ) {
140
+ $this->settings_include( $this->get_default_settings_include() );
141
+ }
142
+
143
+ if ( ! is_array( $this->get_settings_kit_info() ) ) {
144
+ $this->settings_kit_info( $this->get_default_settings_kit_info() );
145
+ }
146
+
147
+ if ( ! is_array( $this->get_settings_selected_custom_post_types() ) && in_array( 'content', $this->settings_include, true ) ) {
148
+ $this->settings_selected_custom_post_types( $this->get_default_settings_custom_post_types() );
149
+ }
150
+
151
+ if ( ! is_array( $this->get_settings_selected_plugins() ) && in_array( 'plugins', $this->settings_include, true ) ) {
152
+ $this->settings_selected_plugins( $this->get_default_settings_selected_plugins() );
153
+ }
154
+ }
155
+
156
+ public function settings_include( $include ) {
157
+ $this->settings_include = $include;
158
+ }
159
+
160
+ public function get_settings_include() {
161
+ return $this->settings_include;
162
+ }
163
+
164
+ private function settings_kit_info( $kit_info ) {
165
+ $this->settings_kit_info = $kit_info;
166
+ }
167
+
168
+ private function get_settings_kit_info() {
169
+ return $this->settings_kit_info;
170
+ }
171
+
172
+ public function settings_selected_custom_post_types( $selected_custom_post_types ) {
173
+ $this->settings_selected_custom_post_types = $selected_custom_post_types;
174
+ }
175
+
176
+ public function get_settings_selected_custom_post_types() {
177
+ return $this->settings_selected_custom_post_types;
178
+ }
179
+
180
+ public function settings_selected_plugins( $plugins ) {
181
+ $this->settings_selected_plugins = $plugins;
182
+ }
183
+
184
+ public function get_settings_selected_plugins() {
185
+ return $this->settings_selected_plugins;
186
+ }
187
+
188
+ /**
189
+ * Get the default settings of which content types should be exported.
190
+ *
191
+ * @return array
192
+ */
193
+ private function get_default_settings_include() {
194
+ return [ 'templates', 'content', 'settings', 'plugins' ];
195
+ }
196
+
197
+ /**
198
+ * Get the default settings of the kit info.
199
+ *
200
+ * @return array
201
+ */
202
+ private function get_default_settings_kit_info() {
203
+ return [
204
+ 'title' => 'kit',
205
+ 'description' => '',
206
+ ];
207
+ }
208
+
209
+ /**
210
+ * Get the default settings of the plugins that should be exported.
211
+ *
212
+ * @return array{name: string, plugin:string, pluginUri: string, version: string}
213
+ */
214
+ private function get_default_settings_selected_plugins() {
215
+ $installed_plugins = Plugin::$instance->wp->get_plugins();
216
+
217
+ return $installed_plugins->map( function ( $item, $key ) {
218
+ return [
219
+ 'name' => $item['Name'],
220
+ 'plugin' => $key,
221
+ 'pluginUri' => $item['PluginURI'],
222
+ 'version' => $item['Version'],
223
+ ];
224
+ } )->all();
225
+ }
226
+
227
+ /**
228
+ * Get the default settings of all the custom post types that should be exported.
229
+ * Should be all the custom post types that are not built in to WordPress and not part of Elementor.
230
+ *
231
+ * @return array
232
+ */
233
+ private function get_default_settings_custom_post_types() {
234
+ return Utils::get_registered_cpt_names();
235
+ }
236
+
237
+ /**
238
+ * Init the zip archive.
239
+ */
240
+ private function init_zip_archive() {
241
+ if ( ! class_exists( '\ZipArchive' ) ) {
242
+ throw new \Error( static::ZIP_ARCHIVE_MODULE_MISSING );
243
+ }
244
+
245
+ $zip = new \ZipArchive();
246
+
247
+ $temp_dir = Plugin::$instance->uploads_manager->create_unique_dir();
248
+
249
+ $zip_file_name = $temp_dir . sanitize_title( $this->settings_kit_info['title'] ) . '.zip';
250
+
251
+ $zip->open( $zip_file_name, \ZipArchive::CREATE | \ZipArchive::OVERWRITE );
252
+
253
+ $this->zip = $zip;
254
+ }
255
+
256
+ /**
257
+ * Init the manifest data and add some basic info to it.
258
+ */
259
+ private function init_manifest_data() {
260
+ $kit_post = Plugin::$instance->kits_manager->get_active_kit()->get_post();
261
+
262
+ $manifest_data = [
263
+ 'name' => sanitize_title( $this->settings_kit_info['title'] ),
264
+ 'title' => $this->settings_kit_info['title'],
265
+ 'description' => $this->settings_kit_info['description'],
266
+ 'author' => get_the_author_meta( 'display_name', $kit_post->post_author ),
267
+ 'version' => Module::FORMAT_VERSION,
268
+ 'elementor_version' => ELEMENTOR_VERSION,
269
+ 'created' => gmdate( 'Y-m-d H:i:s' ),
270
+ 'thumbnail' => get_the_post_thumbnail_url( $kit_post ),
271
+ 'site' => get_site_url(),
272
+ ];
273
+
274
+ $this->manifest_data = $manifest_data;
275
+ }
276
+
277
+ /**
278
+ * Handle the export process output.
279
+ * Add the manifest data from the runner to the manifest.json file.
280
+ * Create files according to the files array that should be exported by the runner.
281
+ *
282
+ * @param array $export_result
283
+ */
284
+ private function handle_export_result( $export_result ) {
285
+ foreach ( $export_result['manifest'] as $data ) {
286
+ $this->manifest_data += $data;
287
+ }
288
+
289
+ if ( isset( $export_result['files']['path'] ) ) {
290
+ $export_result['files'] = [ $export_result['files'] ];
291
+ }
292
+
293
+ foreach ( $export_result['files'] as $file ) {
294
+ $file_extension = pathinfo( $file['path'], PATHINFO_EXTENSION );
295
+ if ( empty( $file_extension ) ) {
296
+ $this->add_json_file(
297
+ $file['path'],
298
+ $file['data']
299
+ );
300
+ } else {
301
+ $this->add_file(
302
+ $file['path'],
303
+ $file['data']
304
+ );
305
+ }
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Add json file to the zip archive.
311
+ *
312
+ * @param string $path The relative path to the file.
313
+ * @param array $content The content of the file.
314
+ * @param int $json_flags
315
+ */
316
+ private function add_json_file( $path, array $content, $json_flags = 0 ) {
317
+ if ( ! Str::ends_with( $path, '.json' ) ) {
318
+ $path .= '.json';
319
+ }
320
+
321
+ $this->add_file( $path, wp_json_encode( $content, $json_flags ) );
322
+ }
323
+
324
+ /**
325
+ * Add file to the zip archive.
326
+ *
327
+ * @param string $file
328
+ * @param string $content The content of the file.
329
+ */
330
+ private function add_file( $file, $content ) {
331
+ $this->zip->addFromString( $file, $content );
332
+ }
333
+ }
app/modules/import-export/processes/import.php ADDED
@@ -0,0 +1,549 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Processes;
4
+
5
+ use Elementor\App\Modules\ImportExport\Compatibility\Base_Adapter;
6
+ use Elementor\App\Modules\ImportExport\Compatibility\Envato;
7
+ use Elementor\App\Modules\ImportExport\Compatibility\Kit_Library;
8
+ use Elementor\App\Modules\ImportExport\Utils;
9
+ use Elementor\Core\Base\Document;
10
+ use Elementor\Plugin;
11
+
12
+ use Elementor\App\Modules\ImportExport\Runners\Import\Elementor_Content;
13
+ use Elementor\App\Modules\ImportExport\Runners\Import\Import_Runner_Base;
14
+ use Elementor\App\Modules\ImportExport\Runners\Import\Plugins;
15
+ use Elementor\App\Modules\ImportExport\Runners\Import\Site_Settings;
16
+ use Elementor\App\Modules\ImportExport\Runners\Import\Taxonomies;
17
+ use Elementor\App\Modules\ImportExport\Runners\Import\Templates;
18
+ use Elementor\App\Modules\ImportExport\Runners\Import\Wp_Content;
19
+ use Elementor\App\Modules\ImportExport\Module;
20
+
21
+ class Import {
22
+ const MANIFEST_ERROR_KEY = 'manifest-error';
23
+
24
+ const SESSION_DOES_NOT_EXITS_ERROR = 'session-does-not-exits-error';
25
+
26
+ const ZIP_FILE_ERROR_KEY = 'zip-file-error';
27
+
28
+ /**
29
+ * @var Import_Runner_Base[]
30
+ */
31
+ protected $runners = [];
32
+
33
+ /**
34
+ * The session ID of the import process.
35
+ * This ID is uniquely generated for each import process (by the temp folder which contains the extracted kit files).
36
+ *
37
+ * @var string
38
+ */
39
+ private $session_id;
40
+
41
+ /**
42
+ * Adapter for the kit compatibility.
43
+ *
44
+ * @var Base_Adapter[]
45
+ */
46
+ private $adapters;
47
+
48
+ /**
49
+ * Document's elements that imported during the process.
50
+ *
51
+ * @var array
52
+ */
53
+ private $documents_elements = [];
54
+
55
+ /**
56
+ * Path to the extracted kit files.
57
+ *
58
+ * @var string
59
+ */
60
+ private $extracted_directory_path;
61
+
62
+ /**
63
+ * Imported kit manifest.
64
+ *
65
+ * @var array
66
+ */
67
+ private $manifest;
68
+
69
+ /**
70
+ * Imported kit site settings. (e.g: custom_colors, custom_typography, etc.)
71
+ *
72
+ * @var array
73
+ */
74
+ private $site_settings;
75
+
76
+ /**
77
+ * Selected content types to import.
78
+ *
79
+ * @var array
80
+ */
81
+ private $settings_include;
82
+
83
+ /**
84
+ * Referer of the import. (e.g: kit-library, local, etc.)
85
+ *
86
+ * @var string
87
+ */
88
+ private $settings_referrer;
89
+
90
+ /**
91
+ * All the conflict between the exited templates and the kit templates.
92
+ *
93
+ * @var array
94
+ */
95
+ private $settings_conflicts;
96
+
97
+ /**
98
+ * Selected elementor templates conditions to override.
99
+ *
100
+ * @var array
101
+ */
102
+ private $settings_selected_override_conditions;
103
+
104
+ /**
105
+ * Selected custom post types to import.
106
+ *
107
+ * @var array
108
+ */
109
+ private $settings_selected_custom_post_types;
110
+
111
+ /**
112
+ * Selected plugins to import.
113
+ *
114
+ * @var array
115
+ */
116
+ private $settings_selected_plugins;
117
+
118
+ /**
119
+ * The imported data output.
120
+ *
121
+ * @var array
122
+ */
123
+ private $imported_data = [];
124
+
125
+ /**
126
+ * The metadata output of the import runners.
127
+ * Will be saved in the import_session and will be used to revert the import process.
128
+ *
129
+ * @var array
130
+ */
131
+ private $runners_import_metadata = [];
132
+
133
+ /**
134
+ * @param $path string session_id | zip_file_path
135
+ * @param $settings array Use to determine which content to import.
136
+ * (e.g: include, selected_plugins, selected_cpt, selected_override_conditions, etc.)
137
+ * @throws \Exception
138
+ */
139
+ public function __construct( string $path, array $settings = [] ) {
140
+ if ( is_file( $path ) ) {
141
+ $this->extracted_directory_path = $this->extract_zip( $path );
142
+ } else {
143
+ $elementor_tmp_directory = Plugin::$instance->uploads_manager->get_temp_dir();
144
+ $path = $elementor_tmp_directory . basename( $path );
145
+
146
+ if ( ! is_dir( $path ) ) {
147
+ throw new \Exception( static::SESSION_DOES_NOT_EXITS_ERROR );
148
+ }
149
+
150
+ $this->extracted_directory_path = $path . '/';
151
+ }
152
+
153
+ $this->session_id = basename( $this->extracted_directory_path );
154
+ $this->settings_referrer = ! empty( $settings['referrer'] ) ? $settings['referrer'] : 'local';
155
+ $this->settings_include = ! empty( $settings['include'] ) ? $settings['include'] : null;
156
+
157
+ // Using isset and not empty is important since empty array is valid option.
158
+ $this->settings_selected_override_conditions = $settings['overrideConditions'] ?? null;
159
+ $this->settings_selected_custom_post_types = $settings['selectedCustomPostTypes'] ?? null;
160
+ $this->settings_selected_plugins = $settings['plugins'] ?? null;
161
+
162
+ $this->manifest = $this->read_manifest_json();
163
+ $this->site_settings = $this->read_site_settings_json();
164
+
165
+ $this->set_default_settings();
166
+ }
167
+
168
+ /**
169
+ * Register a runner.
170
+ * Be aware that the runner will be executed in the order of registration, the order is crucial for the import process.
171
+ *
172
+ * @param Import_Runner_Base $runner_instance
173
+ */
174
+ public function register( Import_Runner_Base $runner_instance ) {
175
+ $this->runners[ $runner_instance::get_name() ] = $runner_instance;
176
+ }
177
+
178
+ public function register_default_runners() {
179
+ $this->register( new Site_Settings() );
180
+ $this->register( new Plugins() );
181
+ $this->register( new Templates() );
182
+ $this->register( new Taxonomies() );
183
+ $this->register( new Elementor_Content() );
184
+ $this->register( new Wp_Content() );
185
+ }
186
+
187
+ /**
188
+ * Set default settings for the import.
189
+ */
190
+ private function set_default_settings() {
191
+ if ( ! is_array( $this->get_settings_include() ) ) {
192
+ $this->settings_include( $this->get_default_settings_include() );
193
+ }
194
+
195
+ if ( ! is_array( $this->get_settings_conflicts() ) ) {
196
+ $this->settings_conflicts( $this->get_default_settings_conflicts() );
197
+ }
198
+
199
+ if ( ! is_array( $this->get_settings_selected_override_conditions() ) ) {
200
+ $this->settings_selected_override_conditions( $this->get_default_settings_override_conditions() );
201
+ }
202
+
203
+ if ( ! is_array( $this->get_settings_selected_custom_post_types() ) ) {
204
+ $this->settings_selected_custom_post_types( $this->get_default_settings_custom_post_types() );
205
+ }
206
+
207
+ if ( ! is_array( $this->get_settings_selected_plugins() ) ) {
208
+ $this->settings_selected_plugins( $this->get_default_settings_plugins() );
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Execute the import process.
214
+ *
215
+ * @return array The imported data output.
216
+ * @throws \Exception
217
+ */
218
+ public function run() {
219
+ $start_time = current_time( 'timestamp' );
220
+
221
+ if ( empty( $this->runners ) ) {
222
+ throw new \Exception( 'Please specify import runners.' );
223
+ }
224
+
225
+ $data = [
226
+ 'session_id' => $this->session_id,
227
+ 'include' => $this->settings_include,
228
+ 'manifest' => $this->manifest,
229
+ 'site_settings' => $this->site_settings,
230
+ 'selected_plugins' => $this->settings_selected_plugins,
231
+ 'extracted_directory_path' => $this->extracted_directory_path,
232
+ 'selected_custom_post_types' => $this->settings_selected_custom_post_types,
233
+ ];
234
+
235
+ add_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10, 2 );
236
+
237
+ // Set the Request's state as an Elementor upload request, in order to support unfiltered file uploads.
238
+ Plugin::$instance->uploads_manager->set_elementor_upload_state( true );
239
+
240
+ foreach ( $this->runners as $runner ) {
241
+ if ( $runner->should_import( $data ) ) {
242
+ $import = $runner->import( $data, $this->imported_data );
243
+ $this->imported_data = array_merge_recursive( $this->imported_data, $import );
244
+
245
+ $this->runners_import_metadata[ $runner::get_name() ] = $runner->get_import_session_metadata();
246
+ }
247
+ }
248
+
249
+ // After the upload complete, set the elementor upload state back to false.
250
+ Plugin::$instance->uploads_manager->set_elementor_upload_state( false );
251
+
252
+ remove_filter( 'elementor/document/save/data', [ $this, 'prevent_saving_elements_on_post_creation' ], 10 );
253
+
254
+ $this->add_import_session_option( $start_time );
255
+
256
+ $this->save_elements_of_imported_posts();
257
+
258
+ Plugin::$instance->uploads_manager->remove_file_or_dir( $this->extracted_directory_path );
259
+ return $this->imported_data;
260
+ }
261
+
262
+ public function get_manifest() {
263
+ return $this->manifest;
264
+ }
265
+
266
+ public function get_extracted_directory_path() {
267
+ return $this->extracted_directory_path;
268
+ }
269
+
270
+ public function get_session_id() {
271
+ return $this->session_id;
272
+ }
273
+
274
+ public function get_adapters() {
275
+ return $this->adapters;
276
+ }
277
+
278
+ /**
279
+ * Get settings by key.
280
+ * Used for backward compatibility.
281
+ *
282
+ * @param string $key The key of the setting.
283
+ */
284
+ public function get_settings( $key ) {
285
+ switch ( $key ) {
286
+ case 'include':
287
+ return $this->get_settings_include();
288
+
289
+ case 'overrideConditions':
290
+ return $this->get_settings_selected_override_conditions();
291
+
292
+ case 'selectedCustomPostTypes':
293
+ return $this->get_settings_selected_custom_post_types();
294
+
295
+ case 'plugins':
296
+ return $this->get_settings_selected_plugins();
297
+
298
+ default:
299
+ return [];
300
+ }
301
+ }
302
+
303
+ public function settings_include( array $settings_include ) {
304
+ $this->settings_include = $settings_include;
305
+
306
+ return $this;
307
+ }
308
+
309
+ public function get_settings_include() {
310
+ return $this->settings_include;
311
+ }
312
+
313
+ public function settings_referrer( $settings_referrer ) {
314
+ $this->settings_referrer = $settings_referrer;
315
+
316
+ return $this;
317
+ }
318
+
319
+ public function get_settings_referrer() {
320
+ return $this->settings_referrer;
321
+ }
322
+
323
+ public function settings_conflicts( array $settings_conflicts ) {
324
+ $this->settings_conflicts = $settings_conflicts;
325
+
326
+ return $this;
327
+ }
328
+
329
+ public function get_settings_conflicts() {
330
+ return $this->settings_conflicts;
331
+ }
332
+
333
+ public function settings_selected_override_conditions( array $settings_selected_override_conditions ) {
334
+ $this->settings_selected_override_conditions = $settings_selected_override_conditions;
335
+
336
+ return $this;
337
+ }
338
+
339
+ public function get_settings_selected_override_conditions() {
340
+ return $this->settings_selected_override_conditions;
341
+ }
342
+
343
+ public function settings_selected_custom_post_types( array $settings_selected_custom_post_types ) {
344
+ $this->settings_selected_custom_post_types = $settings_selected_custom_post_types;
345
+
346
+ return $this;
347
+ }
348
+
349
+ public function get_settings_selected_custom_post_types() {
350
+ return $this->settings_selected_custom_post_types;
351
+ }
352
+
353
+ public function settings_selected_plugins( array $settings_selected_plugins ) {
354
+ $this->settings_selected_plugins = $settings_selected_plugins;
355
+
356
+ return $this;
357
+ }
358
+
359
+ public function get_settings_selected_plugins() {
360
+ return $this->settings_selected_plugins;
361
+ }
362
+
363
+ /**
364
+ * Prevent saving elements on elementor post creation.
365
+ *
366
+ * @param array $data
367
+ * @param Document $document
368
+ *
369
+ * @return array
370
+ */
371
+ public function prevent_saving_elements_on_post_creation( array $data, Document $document ) {
372
+ if ( isset( $data['elements'] ) ) {
373
+ $this->documents_elements[ $document->get_main_id() ] = $data['elements'];
374
+
375
+ $data['elements'] = [];
376
+ }
377
+
378
+ return $data;
379
+ }
380
+
381
+ /**
382
+ * Extract the zip file.
383
+ *
384
+ * @param string $zip_path The path to the zip file.
385
+ * @return string The extracted directory path.
386
+ */
387
+ private function extract_zip( $zip_path ) {
388
+ $extraction_result = Plugin::$instance->uploads_manager->extract_and_validate_zip( $zip_path, [ 'json', 'xml' ] );
389
+
390
+ if ( is_wp_error( $extraction_result ) ) {
391
+ throw new \Error( static::ZIP_FILE_ERROR_KEY );
392
+ }
393
+
394
+ return $extraction_result['extraction_directory'];
395
+ }
396
+
397
+ /**
398
+ * Get the manifest file from the extracted directory and adapt it if needed.
399
+ *
400
+ * @return string The manifest file content.
401
+ */
402
+ private function read_manifest_json() {
403
+ $manifest = Utils::read_json_file( $this->extracted_directory_path . 'manifest' );
404
+
405
+ if ( ! $manifest ) {
406
+ throw new \Error( static::MANIFEST_ERROR_KEY );
407
+ }
408
+
409
+ $this->init_adapters( $manifest );
410
+
411
+ foreach ( $this->adapters as $adapter ) {
412
+ $manifest = $adapter->adapt_manifest( $manifest );
413
+ }
414
+
415
+ return $manifest;
416
+ }
417
+
418
+ /**
419
+ * Init the adapters and determine which ones to use.
420
+ *
421
+ * @param array $manifest_data The manifest file content.
422
+ */
423
+ private function init_adapters( array $manifest_data ) {
424
+ $this->adapters = [];
425
+
426
+ /** @var Base_Adapter[] $adapter_types */
427
+ $adapter_types = [ Envato::class, Kit_Library::class ];
428
+
429
+ foreach ( $adapter_types as $adapter_type ) {
430
+ if ( $adapter_type::is_compatibility_needed( $manifest_data, [ 'referrer' => $this->get_settings_referrer() ] ) ) {
431
+ $this->adapters[] = new $adapter_type( $this );
432
+ }
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Get the site settings file from the extracted directory and adapt it if needed.
438
+ *
439
+ * @return string The site settings file content.
440
+ */
441
+ private function read_site_settings_json() {
442
+ $site_settings = Utils::read_json_file( $this->extracted_directory_path . 'site-settings' );
443
+
444
+ foreach ( $this->adapters as $adapter ) {
445
+ $site_settings = $adapter->adapt_site_settings( $site_settings, $this->manifest, $this->extracted_directory_path );
446
+ }
447
+
448
+ return $site_settings;
449
+ }
450
+
451
+ /**
452
+ * Get all the custom post types in the kit.
453
+ *
454
+ * @return array Custom post types names.
455
+ */
456
+ private function get_default_settings_custom_post_types() {
457
+ if ( empty( $this->manifest['custom-post-type-title'] ) ) {
458
+ return [];
459
+ }
460
+
461
+ $manifest_post_types = array_keys( $this->manifest['custom-post-type-title'] );
462
+
463
+ return array_diff( $manifest_post_types, Utils::get_builtin_wp_post_types() );
464
+ }
465
+
466
+ /**
467
+ * Get the default settings of elementor templates conditions to override.
468
+ *
469
+ * @return array
470
+ */
471
+ private function get_default_settings_conflicts() {
472
+ if ( empty( $this->manifest['templates'] ) ) {
473
+ return [];
474
+ }
475
+
476
+ return apply_filters( 'elementor/import/get_default_settings_conflicts', [], $this->manifest['templates'] );
477
+ }
478
+
479
+ /**
480
+ * Get the default settings of elementor templates conditions to override.
481
+ *
482
+ * @return array
483
+ */
484
+ private function get_default_settings_override_conditions() {
485
+ if ( empty( $this->settings_conflicts ) ) {
486
+ return [];
487
+ }
488
+
489
+ return array_keys( $this->settings_conflicts );
490
+ }
491
+
492
+ /**
493
+ * Get the default settings of the plugins that should be imported.
494
+ *
495
+ * @return array
496
+ */
497
+ private function get_default_settings_plugins() {
498
+ return ! empty( $this->manifest['plugins'] ) ? $this->manifest['plugins'] : [];
499
+ }
500
+
501
+ /**
502
+ * Get the default settings of which content types should be imported.
503
+ *
504
+ * @return array
505
+ */
506
+ private function get_default_settings_include() {
507
+ return [ 'templates', 'plugins', 'content', 'settings' ];
508
+ }
509
+
510
+ /**
511
+ * Get the data that requires updating/replacement when imported.
512
+ *
513
+ * @return array{post_ids: array, term_ids: array}
514
+ */
515
+ private function get_imported_data_replacements() : array {
516
+ return [
517
+ 'post_ids' => Utils::map_old_new_post_ids( $this->imported_data ),
518
+ 'term_ids' => Utils::map_old_new_term_ids( $this->imported_data ),
519
+ ];
520
+ }
521
+
522
+ /**
523
+ * Save the prevented elements on elementor post creation elements.
524
+ * Handle the replacement of all the dynamic content of the elements that probably have been changed during the import.
525
+ */
526
+ private function save_elements_of_imported_posts() {
527
+ foreach ( $this->documents_elements as $new_id => $document_elements ) {
528
+ $document = Plugin::$instance->documents->get( $new_id );
529
+ $updated_elements = $document->on_import_update_dynamic_content( $document_elements, $this->get_imported_data_replacements() );
530
+ $document->save( [ 'elements' => $updated_elements ] );
531
+ }
532
+ }
533
+
534
+ private function add_import_session_option( $start_time ) {
535
+ $import_sessions = get_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS );
536
+
537
+ $import_sessions[ time() ] = [
538
+ 'session_id' => $this->session_id,
539
+ 'kit_name' => $this->manifest['name'],
540
+ 'kit_source' => $this->settings_referrer,
541
+ 'user_id' => get_current_user_id(),
542
+ 'start_timestamp' => $start_time,
543
+ 'end_timestamp' => current_time( 'timestamp' ),
544
+ 'runners' => $this->runners_import_metadata,
545
+ ];
546
+
547
+ update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions, false );
548
+ }
549
+ }
app/modules/import-export/processes/revert.php ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Elementor\App\Modules\ImportExport\Processes;
3
+
4
+ use Elementor\App\Modules\ImportExport\Module;
5
+ use Elementor\App\Modules\ImportExport\Runners\Revert\Elementor_Content;
6
+ use Elementor\App\Modules\ImportExport\Runners\Revert\Revert_Runner_Base;
7
+ use Elementor\App\Modules\ImportExport\Runners\Revert\Plugins;
8
+ use Elementor\App\Modules\ImportExport\Runners\Revert\Site_Settings;
9
+ use Elementor\App\Modules\ImportExport\Runners\Revert\Taxonomies;
10
+ use Elementor\App\Modules\ImportExport\Runners\Revert\Templates;
11
+ use Elementor\App\Modules\ImportExport\Runners\Revert\Wp_Content;
12
+
13
+ class Revert {
14
+
15
+ /**
16
+ * @var Revert_Runner_Base[]
17
+ */
18
+ protected $runners = [];
19
+
20
+ private $import_sessions;
21
+
22
+ private $revert_sessions;
23
+
24
+ /**
25
+ * @throws \Exception
26
+ */
27
+ public function __construct() {
28
+ $this->import_sessions = $this->get_import_sessions();
29
+ $this->revert_sessions = $this->get_revert_sessions();
30
+ }
31
+
32
+ /**
33
+ * Register a runner.
34
+ *
35
+ * @param Revert_Runner_Base $runner_instance
36
+ */
37
+ public function register( Revert_Runner_Base $runner_instance ) {
38
+ $this->runners[ $runner_instance::get_name() ] = $runner_instance;
39
+ }
40
+
41
+ public function register_default_runners() {
42
+ $this->register( new Site_Settings() );
43
+ $this->register( new Plugins() );
44
+ $this->register( new Templates() );
45
+ $this->register( new Taxonomies() );
46
+ $this->register( new Elementor_Content() );
47
+ $this->register( new Wp_Content() );
48
+ }
49
+
50
+ public function run() {
51
+ if ( empty( $this->runners ) ) {
52
+ throw new \Exception( 'Please specify revert runners.' );
53
+ }
54
+
55
+ $data = $this->get_last_import_session();
56
+
57
+ if ( empty( $data ) ) {
58
+ throw new \Exception( 'Could not find any import sessions to revert.' );
59
+ }
60
+
61
+ foreach ( $this->runners as $runner ) {
62
+ if ( $runner->should_revert( $data ) ) {
63
+ $runner->revert( $data );
64
+ }
65
+ }
66
+
67
+ $this->revert_attachments( $data );
68
+
69
+ $this->delete_last_import_data();
70
+ }
71
+
72
+ public function get_import_sessions() {
73
+ $import_sessions = get_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS );
74
+
75
+ if ( ! $import_sessions ) {
76
+ return [];
77
+ }
78
+
79
+ ksort( $import_sessions, SORT_NUMERIC );
80
+
81
+ return $import_sessions;
82
+ }
83
+
84
+ public function get_revert_sessions() {
85
+ $revert_sessions = get_option( Module::OPTION_KEY_ELEMENTOR_REVERT_SESSIONS );
86
+
87
+ if ( ! $revert_sessions ) {
88
+ return [];
89
+ }
90
+
91
+ return $revert_sessions;
92
+ }
93
+
94
+ public function get_last_import_session() {
95
+ $import_sessions = $this->import_sessions;
96
+
97
+ if ( empty( $import_sessions ) ) {
98
+ return [];
99
+ }
100
+
101
+ return end( $import_sessions );
102
+ }
103
+
104
+ public function get_penultimate_import_session() {
105
+ $sessions_data = $this->import_sessions;
106
+ $penultimate_element_value = [];
107
+
108
+ if ( empty( $sessions_data ) ) {
109
+ return [];
110
+ }
111
+
112
+ end( $sessions_data );
113
+
114
+ prev( $sessions_data );
115
+
116
+ if ( ! is_null( key( $sessions_data ) ) ) {
117
+ $penultimate_element_value = current( $sessions_data );
118
+ }
119
+
120
+ return $penultimate_element_value;
121
+ }
122
+
123
+ private function delete_last_import_data() {
124
+ $import_sessions = $this->import_sessions;
125
+ $revert_sessions = $this->revert_sessions;
126
+
127
+ $reverted_session = array_pop( $import_sessions );
128
+
129
+ $revert_sessions[] = [
130
+ 'session_id' => $reverted_session['session_id'],
131
+ 'kit_name' => $reverted_session['kit_name'],
132
+ 'source' => $reverted_session['kit_source'],
133
+ 'user_id' => get_current_user_id(),
134
+ 'import_timestamp' => $reverted_session['start_timestamp'],
135
+ 'revert_timestamp' => current_time( 'timestamp' ),
136
+ ];
137
+
138
+ update_option( Module::OPTION_KEY_ELEMENTOR_IMPORT_SESSIONS, $import_sessions, false );
139
+ update_option( Module::OPTION_KEY_ELEMENTOR_REVERT_SESSIONS, $revert_sessions, false );
140
+
141
+ $this->import_sessions = $import_sessions;
142
+ $this->revert_sessions = $revert_sessions;
143
+ }
144
+
145
+ private function revert_attachments( $data ) {
146
+ $query_args = [
147
+ 'post_type' => 'attachment',
148
+ 'post_status' => 'any',
149
+ 'posts_per_page' => -1,
150
+ 'meta_query' => [
151
+ [
152
+ 'key' => Module::META_KEY_ELEMENTOR_IMPORT_SESSION_ID,
153
+ 'value' => $data['session_id'],
154
+ ],
155
+ ],
156
+ ];
157
+
158
+ $query = new \WP_Query( $query_args );
159
+
160
+ foreach ( $query->posts as $post ) {
161
+ wp_delete_attachment( $post->ID, true );
162
+ }
163
+ }
164
+ }
app/modules/import-export/runners/export/elementor-content.php ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Export;
4
+
5
+ use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
6
+ use Elementor\Plugin;
7
+
8
+ class Elementor_Content extends Export_Runner_Base {
9
+ private $page_on_front_id;
10
+
11
+ public function __construct() {
12
+ $this->init_page_on_front_data();
13
+ }
14
+
15
+ public static function get_name() : string {
16
+ return 'elementor-content';
17
+ }
18
+
19
+ public function should_export( array $data ) {
20
+ return (
21
+ isset( $data['include'] ) &&
22
+ in_array( 'content', $data['include'], true )
23
+ );
24
+ }
25
+
26
+ public function export( array $data ) {
27
+ $elementor_post_types = ImportExportUtils::get_elementor_post_types();
28
+
29
+ $files = [];
30
+ $manifest = [];
31
+
32
+ foreach ( $elementor_post_types as $post_type ) {
33
+ $export = $this->export_elementor_post_type( $post_type );
34
+ $files = array_merge( $files, $export['files'] );
35
+
36
+ $manifest[ $post_type ] = $export['manifest_data'];
37
+ }
38
+
39
+ $manifest_data['content'] = $manifest;
40
+
41
+ return [
42
+ 'files' => $files,
43
+ 'manifest' => [
44
+ $manifest_data,
45
+ ],
46
+ ];
47
+ }
48
+
49
+ private function export_elementor_post_type( $post_type ) {
50
+ $query_args = [
51
+ 'post_type' => $post_type,
52
+ 'post_status' => 'publish',
53
+ 'posts_per_page' => -1,
54
+ 'meta_query' => [
55
+ [
56
+ 'key' => static::META_KEY_ELEMENTOR_EDIT_MODE,
57
+ 'compare' => 'EXISTS',
58
+ ],
59
+ [
60
+ 'key' => '_elementor_data',
61
+ 'compare' => 'EXISTS',
62
+ ],
63
+ [
64
+ 'key' => '_elementor_data',
65
+ 'compare' => '!=',
66
+ 'value' => '[]',
67
+ ],
68
+ ],
69
+ ];
70
+
71
+ $query = new \WP_Query( $query_args );
72
+
73
+ if ( empty( $query ) ) {
74
+ return [
75
+ 'files' => [],
76
+ 'manifest_data' => [],
77
+ ];
78
+ }
79
+
80
+ $post_type_taxonomies = $this->get_post_type_taxonomies( $post_type );
81
+
82
+ $manifest_data = [];
83
+ $files = [];
84
+
85
+ foreach ( $query->posts as $post ) {
86
+ $document = Plugin::$instance->documents->get( $post->ID );
87
+
88
+ $terms = ! empty( $post_type_taxonomies ) ? $this->get_post_terms( $post->ID, $post_type_taxonomies ) : [];
89
+
90
+ $post_manifest_data = [
91
+ 'title' => $post->post_title,
92
+ 'excerpt' => $post->post_excerpt,
93
+ 'doc_type' => $document->get_name(),
94
+ 'thumbnail' => get_the_post_thumbnail_url( $post ),
95
+ 'url' => get_permalink( $post ),
96
+ 'terms' => $terms,
97
+ ];
98
+
99
+ if ( $post->ID === $this->page_on_front_id ) {
100
+ $post_manifest_data['show_on_front'] = true;
101
+ }
102
+
103
+ $manifest_data[ $post->ID ] = $post_manifest_data;
104
+
105
+ $files[] = [
106
+ 'path' => 'content/' . $post_type . '/' . $post->ID,
107
+ 'data' => $document->get_export_data(),
108
+ ];
109
+ }
110
+
111
+ return [
112
+ 'files' => $files,
113
+ 'manifest_data' => $manifest_data,
114
+ ];
115
+ }
116
+
117
+ private function get_post_type_taxonomies( $post_type ) {
118
+ return get_object_taxonomies( $post_type );
119
+ }
120
+
121
+ private function get_post_terms( $post_id, array $taxonomies ) {
122
+ $terms = wp_get_object_terms( $post_id, $taxonomies );
123
+
124
+ $result = [];
125
+
126
+ foreach ( $terms as $term ) {
127
+ $result[] = [
128
+ 'term_id' => $term->term_id,
129
+ 'taxonomy' => $term->taxonomy,
130
+ 'slug' => $term->slug,
131
+ ];
132
+ }
133
+
134
+ return $result;
135
+ }
136
+
137
+ private function init_page_on_front_data() {
138
+ $show_page_on_front = 'page' === get_option( 'show_on_front' );
139
+
140
+ if ( $show_page_on_front ) {
141
+ $this->page_on_front_id = (int) get_option( 'page_on_front' );
142
+ }
143
+ }
144
+ }
app/modules/import-export/runners/export/export-runner-base.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Export;
4
+
5
+ use Elementor\App\Modules\ImportExport\Runners\Runner_Interface;
6
+
7
+ abstract class Export_Runner_Base implements Runner_Interface {
8
+
9
+ /**
10
+ * By the passed data we should decide if we want to run the export function of the runner or not.
11
+ *
12
+ * @param array $data
13
+ *
14
+ * @return bool
15
+ */
16
+ abstract public function should_export( array $data );
17
+
18
+ /**
19
+ * Main function of the runner export process.
20
+ *
21
+ * @param array $data Necessary data for the export process.
22
+ *
23
+ * @return array{files: array, manifest: array}
24
+ * The files that should be part of the kit and the relevant manifest data.
25
+ */
26
+ abstract public function export( array $data );
27
+ }
app/modules/import-export/runners/export/plugins.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Export;
4
+
5
+ class Plugins extends Export_Runner_Base {
6
+
7
+ public static function get_name() : string {
8
+ return 'plugins';
9
+ }
10
+
11
+ public function should_export( array $data ) {
12
+ return (
13
+ isset( $data['include'] ) &&
14
+ in_array( 'plugins', $data['include'], true ) &&
15
+ is_array( $data['selected_plugins'] )
16
+ );
17
+ }
18
+
19
+ public function export( array $data ) {
20
+ $manifest_data['plugins'] = $data['selected_plugins'];
21
+
22
+ return [
23
+ 'manifest' => [
24
+ $manifest_data,
25
+ ],
26
+ 'files' => [],
27
+ ];
28
+ }
29
+ }
app/modules/import-export/runners/export/site-settings.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Elementor\App\Modules\ImportExport\Runners\Export;
3
+
4
+ use Elementor\Plugin;
5
+
6
+ class Site_Settings extends Export_Runner_Base {
7
+
8
+ public static function get_name() : string {
9
+ return 'site-settings';
10
+ }
11
+
12
+ public function should_export( array $data ) {
13
+ return (
14
+ isset( $data['include'] ) &&
15
+ in_array( 'settings', $data['include'], true )
16
+ );
17
+ }
18
+
19
+ public function export( array $data ) {
20
+ $kit = Plugin::$instance->kits_manager->get_active_kit();
21
+ $kit_data = $kit->get_export_data();
22
+ $kit_tabs = $kit->get_tabs();
23
+
24
+ $excluded_kit_settings_keys = [
25
+ 'site_name',
26
+ 'site_description',
27
+ 'site_logo',
28
+ 'site_favicon',
29
+ ];
30
+
31
+ foreach ( $excluded_kit_settings_keys as $setting_key ) {
32
+ unset( $kit_data['settings'][ $setting_key ] );
33
+ }
34
+
35
+ unset( $kit_tabs['settings-site-identity'] );
36
+
37
+ $kit_tabs = array_keys( $kit_tabs );
38
+ $manifest_data['site-settings'] = $kit_tabs;
39
+
40
+ return [
41
+ 'files' => [
42
+ 'path' => 'site-settings',
43
+ 'data' => $kit_data,
44
+ ],
45
+ 'manifest' => [
46
+ $manifest_data,
47
+ ],
48
+ ];
49
+ }
50
+ }
app/modules/import-export/runners/export/taxonomies.php ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Elementor\App\Modules\ImportExport\Runners\Export;
3
+
4
+ use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
5
+
6
+ class Taxonomies extends Export_Runner_Base {
7
+
8
+ public static function get_name() : string {
9
+ return 'taxonomies';
10
+ }
11
+
12
+ public function should_export( array $data ) {
13
+ return (
14
+ isset( $data['include'] ) &&
15
+ in_array( 'content', $data['include'], true )
16
+ );
17
+ }
18
+
19
+ public function export( array $data ) {
20
+ $wp_builtin_post_types = ImportExportUtils::get_builtin_wp_post_types();
21
+ $selected_custom_post_types = isset( $data['selected_custom_post_types'] ) ? $data['selected_custom_post_types'] : [];
22
+ $post_types = array_merge( $wp_builtin_post_types, $selected_custom_post_types );
23
+
24
+ $export = $this->export_taxonomies( $post_types );
25
+
26
+ $manifest_data['taxonomies'] = $export['manifest'];
27
+
28
+ return [
29
+ 'files' => $export['files'],
30
+ 'manifest' => [
31
+ $manifest_data,
32
+ ],
33
+ ];
34
+ }
35
+
36
+ private function export_taxonomies( array $post_types ) {
37
+ $files = [];
38
+ $manifest = [];
39
+
40
+ $taxonomies = get_taxonomies();
41
+
42
+ foreach ( $taxonomies as $taxonomy ) {
43
+ $taxonomy_post_types = get_taxonomy( $taxonomy )->object_type;
44
+ $intersected_post_types = array_intersect( $taxonomy_post_types, $post_types );
45
+
46
+ if ( empty( $intersected_post_types ) ) {
47
+ continue;
48
+ }
49
+
50
+ $data = $this->export_terms( $taxonomy );
51
+
52
+ if ( empty( $data ) ) {
53
+ continue;
54
+ }
55
+
56
+ foreach ( $intersected_post_types as $post_type ) {
57
+ $manifest[ $post_type ][] = $taxonomy;
58
+ }
59
+
60
+ $files[] = [
61
+ 'path' => 'taxonomies/' . $taxonomy,
62
+ 'data' => $data,
63
+ ];
64
+ }
65
+
66
+ return [
67
+ 'files' => $files,
68
+ 'manifest' => $manifest,
69
+ ];
70
+ }
71
+
72
+ private function export_terms( $taxonomy ) {
73
+ $terms = get_terms( [
74
+ 'taxonomy' => (array) $taxonomy,
75
+ 'hide_empty' => true,
76
+ 'get' => 'all',
77
+ ] );
78
+
79
+ $ordered_terms = $this->order_terms( $terms );
80
+
81
+ if ( empty( $ordered_terms ) ) {
82
+ return [];
83
+ }
84
+
85
+ $data = [];
86
+
87
+ foreach ( $ordered_terms as $term ) {
88
+ $data[] = [
89
+ 'term_id' => $term->term_id,
90
+ 'name' => $term->name,
91
+ 'slug' => $term->slug,
92
+ 'taxonomy' => $term->taxonomy,
93
+ 'description' => $term->description,
94
+ 'parent' => $term->parent,
95
+ ];
96
+ }
97
+
98
+ return $data;
99
+ }
100
+
101
+ // Put terms in order with no child going before its parent.
102
+ private function order_terms( array $terms ) {
103
+ $ordered_terms = [];
104
+
105
+ while ( $term = array_shift( $terms ) ) {
106
+ $is_top_level = 0 === $term->parent;
107
+ $is_parent_exits = isset( $ordered_terms[ $term->parent ] );
108
+
109
+ if ( $is_top_level || $is_parent_exits ) {
110
+ $ordered_terms[ $term->term_id ] = $term;
111
+ } else {
112
+ $terms[] = $term;
113
+ }
114
+ }
115
+
116
+ return $ordered_terms;
117
+ }
118
+ }
app/modules/import-export/runners/export/templates.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Export;
4
+
5
+ use Elementor\Core\Base\Document;
6
+ use Elementor\Plugin;
7
+ use Elementor\TemplateLibrary\Source_Local;
8
+ use Elementor\Utils;
9
+
10
+ class Templates extends Export_Runner_Base {
11
+
12
+ public static function get_name() : string {
13
+ return 'templates';
14
+ }
15
+
16
+ public function should_export( array $data ) {
17
+ return (
18
+ Utils::has_pro() &&
19
+ isset( $data['include'] ) &&
20
+ in_array( 'templates', $data['include'], true )
21
+ );
22
+ }
23
+
24
+ public function export( array $data ) {
25
+ $template_types = array_values( Source_Local::get_template_types() );
26
+
27
+ $query_args = [
28
+ 'post_type' => Source_Local::CPT,
29
+ 'post_status' => 'publish',
30
+ 'posts_per_page' => -1,
31
+ 'meta_query' => [
32
+ [
33
+ 'key' => Document::TYPE_META_KEY,
34
+ 'value' => $template_types,
35
+ ],
36
+ ],
37
+ ];
38
+
39
+ $templates_query = new \WP_Query( $query_args );
40
+
41
+ $templates_manifest_data = [];
42
+ $files = [];
43
+
44
+ foreach ( $templates_query->posts as $template_post ) {
45
+ $template_id = $template_post->ID;
46
+
47
+ $template_document = Plugin::$instance->documents->get( $template_id );
48
+
49
+ $templates_manifest_data[ $template_id ] = $template_document->get_export_summary();
50
+
51
+ $files[] = [
52
+ 'path' => 'templates/' . $template_id,
53
+ 'data' => $template_document->get_export_data(),
54
+ ];
55
+ }
56
+
57
+ $manifest_data['templates'] = $templates_manifest_data;
58
+
59
+ return [
60
+ 'files' => $files,
61
+ 'manifest' => [
62
+ $manifest_data,
63
+ ],
64
+ ];
65
+ }
66
+ }
app/modules/import-export/runners/export/wp-content.php ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Export;
4
+
5
+ use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
6
+ use Elementor\Core\Utils\ImportExport\WP_Exporter;
7
+
8
+ class Wp_Content extends Export_Runner_Base {
9
+
10
+ public static function get_name() : string {
11
+ return 'wp-content';
12
+ }
13
+
14
+ public function should_export( array $data ) {
15
+ return (
16
+ isset( $data['include'] ) &&
17
+ in_array( 'content', $data['include'], true )
18
+ );
19
+ }
20
+
21
+ public function export( array $data ) {
22
+ $post_types = ImportExportUtils::get_builtin_wp_post_types();
23
+ $custom_post_types = isset( $data['selected_custom_post_types'] ) ? $data['selected_custom_post_types'] : [];
24
+
25
+ $files = [];
26
+ $manifest_data = [];
27
+
28
+ foreach ( $post_types as $post_type ) {
29
+ $export = $this->export_wp_post_type( $post_type );
30
+ $files[] = $export['file'];
31
+ $manifest_data['wp-content'][ $post_type ] = $export['manifest_data'];
32
+ }
33
+
34
+ foreach ( $custom_post_types as $post_type ) {
35
+ $export = $this->export_wp_post_type( $post_type );
36
+ $files[] = $export['file'];
37
+ $manifest_data['wp-content'][ $post_type ] = $export['manifest_data'];
38
+
39
+ $post_type_object = get_post_type_object( $post_type );
40
+
41
+ $manifest_data['custom-post-type-title'][ $post_type ] = [
42
+ 'name' => $post_type_object->name,
43
+ 'label' => $post_type_object->label,
44
+ ];
45
+ }
46
+
47
+ return [
48
+ 'files' => $files,
49
+ 'manifest' => [
50
+ $manifest_data,
51
+ ],
52
+ ];
53
+ }
54
+
55
+ private function export_wp_post_type( $post_type ) {
56
+ $wp_exporter = new WP_Exporter( [
57
+ 'content' => $post_type,
58
+ 'status' => 'publish',
59
+ 'limit' => 20,
60
+ 'meta_query' => [
61
+ [
62
+ 'key' => static::META_KEY_ELEMENTOR_EDIT_MODE,
63
+ 'compare' => 'NOT EXISTS',
64
+ ],
65
+ ],
66
+ 'include_post_featured_image_as_attachment' => true,
67
+ ] );
68
+
69
+ $export_result = $wp_exporter->run();
70
+
71
+ return [
72
+ 'file' => [
73
+ 'path' => 'wp-content/' . $post_type . '/' . $post_type . '.xml',
74
+ 'data' => $export_result['xml'],
75
+ ],
76
+ 'manifest_data' => $export_result['ids'],
77
+ ];
78
+ }
79
+ }
app/modules/import-export/runners/import/elementor-content.php ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Import;
4
+
5
+ use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
6
+ use Elementor\Plugin;
7
+
8
+ class Elementor_Content extends Import_Runner_Base {
9
+
10
+ private $show_page_on_front;
11
+
12
+ private $page_on_front_id;
13
+
14
+ private $import_session_id;
15
+
16
+ public function __construct() {
17
+ $this->init_page_on_front_data();
18
+ }
19
+
20
+ public static function get_name() : string {
21
+ return 'elementor-content';
22
+ }
23
+
24
+ public function should_import( array $data ) {
25
+ return (
26
+ isset( $data['include'] ) &&
27
+ in_array( 'content', $data['include'], true ) &&
28
+ ! empty( $data['manifest']['content'] ) &&
29
+ ! empty( $data['extracted_directory_path'] )
30
+ );
31
+ }
32
+
33
+ public function import( array $data, array $imported_data ) {
34
+ $result['content'] = [];
35
+ $this->import_session_id = $data['session_id'];
36
+
37
+ $elementor_post_types = ImportExportUtils::get_elementor_post_types();
38
+
39
+ foreach ( $elementor_post_types as $post_type ) {
40
+ if ( empty( $data['manifest']['content'][ $post_type ] ) ) {
41
+ continue;
42
+ }
43
+
44
+ $posts_settings = $data['manifest']['content'][ $post_type ];
45
+ $path = $data['extracted_directory_path'] . 'content/' . $post_type . '/';
46
+ $imported_terms = ! empty( $imported_data['taxonomies'] )
47
+ ? ImportExportUtils::map_old_new_term_ids( $imported_data )
48
+ : [];
49
+
50
+ $result['content'][ $post_type ] = $this->import_elementor_post_type( $posts_settings, $path, $post_type, $imported_terms );
51
+ }
52
+
53
+ return $result;
54
+ }
55
+
56
+ private function import_elementor_post_type( array $posts_settings, $path, $post_type, array $imported_terms ) {
57
+ $result = [
58
+ 'succeed' => [],
59
+ 'failed' => [],
60
+ ];
61
+
62
+ foreach ( $posts_settings as $id => $post_settings ) {
63
+ try {
64
+ $post_data = ImportExportUtils::read_json_file( $path . $id );
65
+ $import = $this->import_post( $post_settings, $post_data, $post_type, $imported_terms );
66
+
67
+ if ( is_wp_error( $import ) ) {
68
+ $result['failed'][ $id ] = $import->get_error_message();
69
+ continue;
70
+ }
71
+
72
+ $result['succeed'][ $id ] = $import;
73
+ } catch ( \Exception $error ) {
74
+ $result['failed'][ $id ] = $error->getMessage();
75
+ }
76
+ }
77
+
78
+ return $result;
79
+ }
80
+
81
+ private function import_post( array $post_settings, array $post_data, $post_type, array $imported_terms ) {
82
+ $post_attributes = [
83
+ 'post_title' => $post_settings['title'],
84
+ 'post_type' => $post_type,
85
+ 'post_status' => 'publish',
86
+ ];
87
+
88
+ if ( ! empty( $post_settings['excerpt'] ) ) {
89
+ $post_attributes['post_excerpt'] = $post_settings['excerpt'];
90
+ }
91
+
92
+ $new_document = Plugin::$instance->documents->create(
93
+ $post_settings['doc_type'],
94
+ $post_attributes
95
+ );
96
+
97
+ if ( is_wp_error( $new_document ) ) {
98
+ return $new_document;
99
+ }
100
+
101
+ $post_data['import_settings'] = $post_settings;
102
+
103
+ $new_attachment_callback = function( $attachment_id ) {
104
+ $this->set_session_post_meta( $attachment_id, $this->import_session_id );
105
+ };
106
+
107
+ add_filter( 'elementor/template_library/import_images/new_attachment', $new_attachment_callback );
108
+
109
+ $new_document->import( $post_data );
110
+
111
+ remove_filter( 'elementor/template_library/import_images/new_attachment', $new_attachment_callback );
112
+
113
+ $new_post_id = $new_document->get_main_id();
114
+
115
+ if ( ! empty( $post_settings['terms'] ) ) {
116
+ $this->set_post_terms( $new_post_id, $post_settings['terms'], $imported_terms );
117
+ }
118
+
119
+ if ( ! empty( $post_settings['show_on_front'] ) ) {
120
+ $this->set_page_on_front( $new_post_id );
121
+ }
122
+
123
+ $this->set_session_post_meta( $new_post_id, $this->import_session_id );
124
+
125
+ return $new_post_id;
126
+ }
127
+
128
+ private function set_post_terms( $post_id, array $terms, array $imported_terms ) {
129
+ foreach ( $terms as $term ) {
130
+ if ( ! isset( $imported_terms[ $term['term_id'] ] ) ) {
131
+ continue;
132
+ }
133
+
134
+ wp_set_post_terms( $post_id, [ $imported_terms[ $term['term_id'] ] ], $term['taxonomy'], false );
135
+ }
136
+ }
137
+
138
+ private function init_page_on_front_data() {
139
+ $this->show_page_on_front = 'page' === get_option( 'show_on_front' );
140
+
141
+ if ( $this->show_page_on_front ) {
142
+ $this->page_on_front_id = (int) get_option( 'page_on_front' );
143
+ }
144
+ }
145
+
146
+ private function set_page_on_front( $page_id ) {
147
+ update_option( 'page_on_front', $page_id );
148
+
149
+ if ( ! $this->show_page_on_front ) {
150
+ update_option( 'show_on_front', 'page' );
151
+ }
152
+ }
153
+
154
+ public function get_import_session_metadata() : array {
155
+ return [
156
+ 'page_on_front' => $this->page_on_front_id ?? 0,
157
+ ];
158
+ }
159
+ }
app/modules/import-export/runners/import/import-runner-base.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Import;
4
+
5
+ use Elementor\App\Modules\ImportExport\Runners\Runner_Interface;
6
+
7
+ abstract class Import_Runner_Base implements Runner_Interface {
8
+
9
+ /**
10
+ * By the passed data we should decide if we want to run the import function of the runner or not.
11
+ *
12
+ * @param array $data
13
+ *
14
+ * @return bool
15
+ */
16
+ abstract public function should_import( array $data );
17
+
18
+ /**
19
+ * Main function of the runner import process.
20
+ *
21
+ * @param array $data Necessary data for the import process.
22
+ * @param array $imported_data Data that already imported by previously runners.
23
+ *
24
+ * @return array The result of the import process
25
+ */
26
+ abstract public function import( array $data, array $imported_data );
27
+
28
+ public function get_import_session_metadata() : array {
29
+ return [];
30
+ }
31
+
32
+ public function set_session_post_meta( $post_id, $meta_value ) {
33
+ update_post_meta( $post_id, static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, $meta_value );
34
+ }
35
+
36
+ public function set_session_term_meta( $term_id, $meta_value ) {
37
+ update_term_meta( $term_id, static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID, $meta_value );
38
+ }
39
+ }
app/modules/import-export/runners/import/plugins.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Import;
4
+
5
+ use Elementor\Core\Utils\Collection;
6
+ use Elementor\Core\Utils\Plugins_Manager;
7
+ use Elementor\Core\Utils\Str;
8
+
9
+ class Plugins extends Import_Runner_Base {
10
+
11
+ /**
12
+ * @var Plugins_Manager
13
+ */
14
+ private $plugins_manager;
15
+
16
+ public function __construct( $plugins_manager = null ) {
17
+ if ( $plugins_manager ) {
18
+ $this->plugins_manager = $plugins_manager;
19
+ } else {
20
+ $this->plugins_manager = new Plugins_Manager();
21
+ }
22
+ }
23
+
24
+ public static function get_name() : string {
25
+ return 'plugins';
26
+ }
27
+
28
+ public function should_import( array $data ) {
29
+ return (
30
+ isset( $data['include'] ) &&
31
+ in_array( 'plugins', $data['include'], true ) &&
32
+ ! empty( $data['manifest']['plugins'] ) &&
33
+ ! empty( $data['selected_plugins'] )
34
+ );
35
+ }
36
+
37
+ public function import( array $data, array $imported_data ) {
38
+ $plugins = $data['selected_plugins'];
39
+
40
+ $plugins_collection = ( new Collection( $plugins ) )
41
+ ->map( function ( $item ) {
42
+ if ( ! Str::ends_with( $item['plugin'], '.php' ) ) {
43
+ $item['plugin'] .= '.php';
44
+ }
45
+ return $item;
46
+ } );
47
+
48
+ $slugs = $plugins_collection
49
+ ->map( function ( $item ) {
50
+ return $item['plugin'];
51
+ } )
52
+ ->all();
53
+
54
+ $installed = $this->plugins_manager->install( $slugs );
55
+ $activated = $this->plugins_manager->activate( $installed['succeeded'] );
56
+
57
+ $ordered_activated_plugins = $plugins_collection
58
+ ->filter( function ( $item ) use ( $activated ) {
59
+ return in_array( $item['plugin'], $activated['succeeded'], true );
60
+ } )
61
+ ->map( function ( $item ) {
62
+ return $item['name'];
63
+ } )
64
+ ->all();
65
+
66
+ $result['plugins'] = $ordered_activated_plugins;
67
+
68
+ return $result;
69
+ }
70
+ }
app/modules/import-export/runners/import/site-settings.php ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Import;
4
+
5
+ use Elementor\Plugin;
6
+ use Elementor\Core\Settings\Page\Manager as PageManager;
7
+
8
+ class Site_Settings extends Import_Runner_Base {
9
+
10
+ /**
11
+ * @var int
12
+ */
13
+ private $previous_kit_id;
14
+
15
+ /**
16
+ * @var int
17
+ */
18
+ private $active_kit_id;
19
+
20
+ /**
21
+ * @var int
22
+ */
23
+ private $imported_kit_id;
24
+
25
+ public static function get_name() : string {
26
+ return 'site-settings';
27
+ }
28
+
29
+ public function should_import( array $data ) {
30
+ return (
31
+ isset( $data['include'] ) &&
32
+ in_array( 'settings', $data['include'], true ) &&
33
+ ! empty( $data['site_settings']['settings'] )
34
+ );
35
+ }
36
+
37
+ public function import( array $data, array $imported_data ) {
38
+ $new_site_settings = $data['site_settings']['settings'];
39
+ $title = $data['manifest']['title'] ?? 'Imported Kit';
40
+
41
+ $active_kit = Plugin::$instance->kits_manager->get_active_kit();
42
+
43
+ $this->active_kit_id = (int) $active_kit->get_id();
44
+ $this->previous_kit_id = (int) Plugin::$instance->kits_manager->get_previous_id();
45
+
46
+ $result = [];
47
+
48
+ $old_settings = $active_kit->get_meta( PageManager::META_KEY );
49
+
50
+ if ( ! $old_settings ) {
51
+ $old_settings = [];
52
+ }
53
+
54
+ if ( ! empty( $old_settings['custom_colors'] ) ) {
55
+ $new_site_settings['custom_colors'] = array_merge( $old_settings['custom_colors'], $new_site_settings['custom_colors'] );
56
+ }
57
+
58
+ if ( ! empty( $old_settings['custom_typography'] ) ) {
59
+ $new_site_settings['custom_typography'] = array_merge( $old_settings['custom_typography'], $new_site_settings['custom_typography'] );
60
+ }
61
+
62
+ $new_site_settings = array_replace_recursive( $old_settings, $new_site_settings );
63
+
64
+ $new_kit = Plugin::$instance->kits_manager->create_new_kit( $title, $new_site_settings );
65
+
66
+ $this->imported_kit_id = (int) $new_kit;
67
+
68
+ $result['site-settings'] = (bool) $new_kit;
69
+
70
+ return $result;
71
+ }
72
+
73
+ public function get_import_session_metadata() : array {
74
+ return [
75
+ 'previous_kit_id' => $this->previous_kit_id,
76
+ 'active_kit_id' => $this->active_kit_id,
77
+ 'imported_kit_id' => $this->imported_kit_id,
78
+ ];
79
+ }
80
+ }
app/modules/import-export/runners/import/taxonomies.php ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Import;
4
+
5
+ use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
6
+
7
+ class Taxonomies extends Import_Runner_Base {
8
+
9
+ private $import_session_id;
10
+
11
+ public static function get_name() : string {
12
+ return 'taxonomies';
13
+ }
14
+
15
+ public function should_import( array $data ) {
16
+ return (
17
+ isset( $data['include'] ) &&
18
+ in_array( 'content', $data['include'], true ) &&
19
+ ! empty( $data['extracted_directory_path'] ) &&
20
+ ! empty( $data['manifest']['taxonomies'] )
21
+ );
22
+ }
23
+
24
+ public function import( array $data, array $imported_data ) {
25
+ $path = $data['extracted_directory_path'] . 'taxonomies/';
26
+ $this->import_session_id = $data['session_id'];
27
+
28
+ $wp_builtin_post_types = ImportExportUtils::get_builtin_wp_post_types();
29
+ $selected_custom_post_types = isset( $data['selected_custom_post_types'] ) ? $data['selected_custom_post_types'] : [];
30
+ $post_types = array_merge( $wp_builtin_post_types, $selected_custom_post_types );
31
+
32
+ $result = [];
33
+
34
+ foreach ( $post_types as $post_type ) {
35
+ if ( empty( $data['manifest']['taxonomies'][ $post_type ] ) ) {
36
+ continue;
37
+ }
38
+
39
+ $result['taxonomies'][ $post_type ] = $this->import_taxonomies( $data['manifest']['taxonomies'][ $post_type ], $path );
40
+ }
41
+
42
+ return $result;
43
+ }
44
+
45
+ private function import_taxonomies( array $taxonomies, $path ) {
46
+ $result = [];
47
+ $imported_taxonomies = [];
48
+
49
+ foreach ( $taxonomies as $taxonomy ) {
50
+ if ( ! taxonomy_exists( $taxonomy ) ) {
51
+ continue;
52
+ }
53
+
54
+ if ( ! empty( $imported_taxonomies[ $taxonomy ] ) ) {
55
+ $result[ $taxonomy ] = $imported_taxonomies[ $taxonomy ];
56
+ continue;
57
+ }
58
+
59
+ $taxonomy_data = ImportExportUtils::read_json_file( $path . $taxonomy );
60
+ if ( empty( $taxonomy_data ) ) {
61
+ continue;
62
+ }
63
+
64
+ $import = $this->import_taxonomy( $taxonomy_data );
65
+ $result[ $taxonomy ] = $import;
66
+ $imported_taxonomies[ $taxonomy ] = $import;
67
+ }
68
+
69
+ return $result;
70
+ }
71
+
72
+ private function import_taxonomy( array $taxonomy_data ) {
73
+ $terms = [];
74
+
75
+ foreach ( $taxonomy_data as $term ) {
76
+ $old_slug = $term['slug'];
77
+
78
+ $existing_term = term_exists( $term['slug'], $term['taxonomy'] );
79
+ if ( $existing_term ) {
80
+ if ( 'nav_menu' === $term['taxonomy'] ) {
81
+ $term = $this->handle_duplicated_nav_menu_term( $term );
82
+ } else {
83
+ $terms[] = [
84
+ 'old_id' => (int) $term['term_id'],
85
+ 'new_id' => (int) $existing_term['term_id'],
86
+ 'old_slug' => $old_slug,
87
+ 'new_slug' => $term['slug'],
88
+ ];
89
+ continue;
90
+ }
91
+ }
92
+
93
+ $parent = $this->get_term_parent( $term, $terms );
94
+
95
+ $args = [
96
+ 'slug' => $term['slug'],
97
+ 'description' => wp_slash( $term['description'] ),
98
+ 'parent' => (int) $parent,
99
+ ];
100
+
101
+ $new_term = wp_insert_term( wp_slash( $term['name'] ), $term['taxonomy'], $args );
102
+ if ( ! is_wp_error( $new_term ) ) {
103
+ $this->set_session_term_meta( (int) $new_term['term_id'], $this->import_session_id );
104
+
105
+ $terms[] = [
106
+ 'old_id' => $term['term_id'],
107
+ 'new_id' => (int) $new_term['term_id'],
108
+ 'old_slug' => $old_slug,
109
+ 'new_slug' => $term['slug'],
110
+ ];
111
+ }
112
+ }
113
+
114
+ return $terms;
115
+ }
116
+
117
+ private function handle_duplicated_nav_menu_term( $term ) {
118
+ do {
119
+ $term['slug'] = $term['slug'] . '-duplicate';
120
+ $term['name'] = $term['name'] . ' duplicate';
121
+ } while ( term_exists( $term['slug'], 'nav_menu' ) );
122
+
123
+ return $term;
124
+ }
125
+
126
+ private function get_term_parent( $term, array $imported_terms ) {
127
+ $parent = $term['parent'];
128
+ if ( 0 !== $parent && ! empty( $imported_terms ) ) {
129
+ foreach ( $imported_terms as $imported_term ) {
130
+ if ( $parent === $imported_term['old_id'] ) {
131
+ $parent_term = term_exists( $imported_term['new_id'], $term['taxonomy'] );
132
+ break;
133
+ }
134
+ }
135
+
136
+ if ( isset( $parent_term['term_id'] ) ) {
137
+ return $parent_term['term_id'];
138
+ }
139
+ }
140
+
141
+ return 0;
142
+ }
143
+ }
app/modules/import-export/runners/import/templates.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Import;
4
+
5
+ use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
6
+ use Elementor\Plugin;
7
+ use Elementor\TemplateLibrary\Source_Local;
8
+ use Elementor\Utils;
9
+
10
+ class Templates extends Import_Runner_Base {
11
+ private $import_session_id;
12
+
13
+ public static function get_name() : string {
14
+ return 'templates';
15
+ }
16
+
17
+ public function should_import( array $data ) {
18
+ return (
19
+ Utils::has_pro() &&
20
+ isset( $data['include'] ) &&
21
+ in_array( 'templates', $data['include'], true ) &&
22
+ ! empty( $data['extracted_directory_path'] ) &&
23
+ ! empty( $data['manifest']['templates'] )
24
+ );
25
+ }
26
+
27
+ public function import( array $data, array $imported_data ) {
28
+ $this->import_session_id = $data['session_id'];
29
+
30
+ $path = $data['extracted_directory_path'] . 'templates/';
31
+ $templates = $data['manifest']['templates'];
32
+
33
+ $result['templates'] = [
34
+ 'succeed' => [],
35
+ 'failed' => [],
36
+ ];
37
+
38
+ foreach ( $templates as $id => $template_settings ) {
39
+ try {
40
+ $template_data = ImportExportUtils::read_json_file( $path . $id );
41
+ $import = $this->import_template( $id, $template_settings, $template_data );
42
+
43
+ $result['templates']['succeed'][ $id ] = $import;
44
+ } catch ( \Exception $error ) {
45
+ $result['templates']['failed'][ $id ] = $error->getMessage();
46
+ }
47
+ }
48
+
49
+ return $result;
50
+ }
51
+
52
+ private function import_template( $id, array $template_settings, array $template_data ) {
53
+ $doc_type = $template_settings['doc_type'];
54
+
55
+ $new_document = Plugin::$instance->documents->create(
56
+ $doc_type,
57
+ [
58
+ 'post_title' => $template_settings['title'],
59
+ 'post_type' => Source_Local::CPT,
60
+ 'post_status' => 'publish',
61
+ ]
62
+ );
63
+
64
+ if ( is_wp_error( $new_document ) ) {
65
+ throw new \Exception( $new_document->get_error_message() );
66
+ }
67
+
68
+ $template_data['import_settings'] = $template_settings;
69
+ $template_data['id'] = $id;
70
+
71
+ $new_attachment_callback = function( $attachment_id ) {
72
+ $this->set_session_post_meta( $attachment_id, $this->import_session_id );
73
+ };
74
+
75
+ add_filter( 'elementor/template_library/import_images/new_attachment', $new_attachment_callback );
76
+
77
+ $new_document->import( $template_data );
78
+
79
+ remove_filter( 'elementor/template_library/import_images/new_attachment', $new_attachment_callback );
80
+
81
+ $document_id = $new_document->get_main_id();
82
+
83
+ $this->set_session_post_meta( $document_id, $this->import_session_id );
84
+
85
+ return $document_id;
86
+ }
87
+ }
app/modules/import-export/runners/import/wp-content.php ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Import;
4
+
5
+ use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
6
+ use Elementor\Core\Utils\ImportExport\WP_Import;
7
+
8
+ class Wp_Content extends Import_Runner_Base {
9
+
10
+ private $import_session_id;
11
+
12
+ /**
13
+ * @var array
14
+ */
15
+ private $selected_custom_post_types = [];
16
+
17
+ public static function get_name() : string {
18
+ return 'wp-content';
19
+ }
20
+
21
+ public function should_import( array $data ) {
22
+ return (
23
+ isset( $data['include'] ) &&
24
+ in_array( 'content', $data['include'], true ) &&
25
+ ! empty( $data['extracted_directory_path'] ) &&
26
+ ! empty( $data['manifest']['wp-content'] )
27
+ );
28
+ }
29
+
30
+ public function import( array $data, array $imported_data ) {
31
+ $this->import_session_id = $data['session_id'];
32
+
33
+ $path = $data['extracted_directory_path'] . 'wp-content/';
34
+
35
+ $post_types = $this->filter_post_types( $data['selected_custom_post_types'] );
36
+
37
+ $taxonomies = $imported_data['taxonomies'] ?? [];
38
+ $imported_terms = ImportExportUtils::map_old_new_term_ids( $imported_data );
39
+
40
+ $result['wp-content'] = [];
41
+
42
+ foreach ( $post_types as $post_type ) {
43
+ $import = $this->import_wp_post_type(
44
+ $path,
45
+ $post_type,
46
+ $imported_data,
47
+ $taxonomies,
48
+ $imported_terms
49
+ );
50
+
51
+ if ( empty( $import ) ) {
52
+ continue;
53
+ }
54
+
55
+ $result['wp-content'][ $post_type ] = $import;
56
+ $imported_data = array_merge( $imported_data, $result );
57
+ }
58
+
59
+ return $result;
60
+ }
61
+
62
+ private function import_wp_post_type( $path, $post_type, array $imported_data, array $taxonomies, array $imported_terms ) {
63
+ $args = [
64
+ 'fetch_attachments' => true,
65
+ 'posts' => ImportExportUtils::map_old_new_post_ids( $imported_data ),
66
+ 'terms' => $imported_terms,
67
+ 'taxonomies' => ! empty( $taxonomies[ $post_type ] ) ? $taxonomies[ $post_type ] : [],
68
+ 'posts_meta' => [
69
+ static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID => $this->import_session_id,
70
+ ],
71
+ 'terms_meta' => [
72
+ static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID => $this->import_session_id,
73
+ ],
74
+ ];
75
+
76
+ $file = $path . $post_type . '/' . $post_type . '.xml';
77
+
78
+ if ( ! file_exists( $file ) ) {
79
+ return [];
80
+ }
81
+
82
+ $wp_importer = new WP_Import( $file, $args );
83
+ $result = $wp_importer->run();
84
+
85
+ return $result['summary']['posts'];
86
+ }
87
+
88
+ private function filter_post_types( $selected_custom_post_types = [] ) {
89
+ $wp_builtin_post_types = ImportExportUtils::get_builtin_wp_post_types();
90
+
91
+ foreach ( $selected_custom_post_types as $custom_post_type ) {
92
+ if ( post_type_exists( $custom_post_type ) ) {
93
+ $this->selected_custom_post_types[] = $custom_post_type;
94
+ }
95
+ }
96
+
97
+ $post_types = array_merge( $wp_builtin_post_types, $this->selected_custom_post_types );
98
+ $post_types = $this->force_element_to_be_last_by_value( $post_types, 'nav_menu_item' );
99
+
100
+ return $post_types;
101
+ }
102
+
103
+ public function get_import_session_metadata() : array {
104
+ return [
105
+ 'custom_post_types' => $this->selected_custom_post_types,
106
+ ];
107
+ }
108
+
109
+ /**
110
+ * @param $array array The array we want to relocate his element.
111
+ * @param $element mixed The value of the element in the array we want to shift to end of the array.
112
+ * @return mixed
113
+ */
114
+ private function force_element_to_be_last_by_value( array $array, $element ) {
115
+ $index = array_search( $element, $array, true );
116
+
117
+ if ( false !== $index ) {
118
+ unset( $array[ $index ] );
119
+ $array[] = $element;
120
+ }
121
+
122
+ return $array;
123
+ }
124
+ }
app/modules/import-export/runners/revert/elementor-content.php ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Revert;
4
+
5
+ use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
6
+ use Elementor\Plugin;
7
+
8
+ class Elementor_Content extends Revert_Runner_Base {
9
+ private $show_page_on_front;
10
+
11
+ private $page_on_front_id;
12
+
13
+ public function __construct() {
14
+ $this->init_page_on_front_data();
15
+ }
16
+
17
+ public static function get_name() : string {
18
+ return 'elementor-content';
19
+ }
20
+
21
+ public function should_revert( array $data ) : bool {
22
+ return (
23
+ isset( $data['runners'] ) &&
24
+ array_key_exists( static::get_name(), $data['runners'] )
25
+ );
26
+ }
27
+
28
+ public function revert( array $data ) {
29
+ $elementor_post_types = ImportExportUtils::get_elementor_post_types();
30
+
31
+ $query_args = [
32
+ 'post_type' => $elementor_post_types,
33
+ 'post_status' => 'any',
34
+ 'posts_per_page' => -1,
35
+ 'meta_query' => [
36
+ [
37
+ 'key' => static::META_KEY_ELEMENTOR_EDIT_MODE,
38
+ 'compare' => 'EXISTS',
39
+ ],
40
+ [
41
+ 'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID,
42
+ 'value' => $data['session_id'],
43
+ ],
44
+ ],
45
+ ];
46
+
47
+ $query = new \WP_Query( $query_args );
48
+
49
+ foreach ( $query->posts as $post ) {
50
+ $post_type_document = Plugin::$instance->documents->get( $post->ID );
51
+ $post_type_document->delete();
52
+
53
+ // Deleting the post will reset the show_on_front option. We need to set it to false,
54
+ // so we can set it back to what it was.
55
+ if ( $post->ID === $this->page_on_front_id ) {
56
+ $this->show_page_on_front = false;
57
+ }
58
+ }
59
+
60
+ $this->restore_page_on_front( $data );
61
+ }
62
+
63
+ private function init_page_on_front_data() {
64
+ $this->show_page_on_front = 'page' === get_option( 'show_on_front' );
65
+
66
+ if ( $this->show_page_on_front ) {
67
+ $this->page_on_front_id = (int) get_option( 'page_on_front' );
68
+ }
69
+ }
70
+
71
+ private function restore_page_on_front( $data ) {
72
+ if ( empty( $data['runners'][ static::get_name() ]['page_on_front'] ) ) {
73
+ return;
74
+ }
75
+
76
+ $page_on_front = $data['runners'][ static::get_name() ]['page_on_front'];
77
+
78
+ $document = Plugin::$instance->documents->get( $page_on_front );
79
+
80
+ if ( ! $document ) {
81
+ return;
82
+ }
83
+
84
+ $this->set_page_on_front( $document->get_main_id() );
85
+ }
86
+
87
+ private function set_page_on_front( $page_id ) {
88
+ update_option( 'page_on_front', $page_id );
89
+
90
+ if ( ! $this->show_page_on_front ) {
91
+ update_option( 'show_on_front', 'page' );
92
+ }
93
+ }
94
+ }
app/modules/import-export/runners/revert/plugins.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Revert;
4
+
5
+ class Plugins extends Revert_Runner_Base {
6
+
7
+ public static function get_name() : string {
8
+ return 'plugins';
9
+ }
10
+
11
+ public function should_revert( array $data ) : bool {
12
+ return false;
13
+ }
14
+
15
+ public function revert( array $data ) {}
16
+ }
app/modules/import-export/runners/revert/revert-runner-base.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Revert;
4
+
5
+ use Elementor\App\Modules\ImportExport\Runners\Runner_Interface;
6
+
7
+ abstract class Revert_Runner_Base implements Runner_Interface {
8
+
9
+ /**
10
+ * By the passed data we should decide if we want to run the revert function of the runner or not.
11
+ *
12
+ * @param array $data
13
+ *
14
+ * @return bool
15
+ */
16
+ abstract public function should_revert( array $data ) : bool;
17
+
18
+ /**
19
+ * Main function of the runner revert process.
20
+ *
21
+ * @param array $data Necessary data for the revert process.
22
+ */
23
+ abstract public function revert( array $data );
24
+ }
app/modules/import-export/runners/revert/site-settings.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Revert;
4
+
5
+ use Elementor\Plugin;
6
+
7
+ class Site_Settings extends Revert_Runner_Base {
8
+
9
+ public static function get_name() : string {
10
+ return 'site-settings';
11
+ }
12
+
13
+ public function should_revert( array $data ) : bool {
14
+ return (
15
+ isset( $data['runners'] ) &&
16
+ array_key_exists( static::get_name(), $data['runners'] )
17
+ );
18
+ }
19
+
20
+ public function revert( array $data ) {
21
+ Plugin::$instance->kits_manager->revert(
22
+ $data['runners'][ static::get_name() ]['imported_kit_id'],
23
+ $data['runners'][ static::get_name() ]['active_kit_id'],
24
+ $data['runners'][ static::get_name() ]['previous_kit_id']
25
+ );
26
+ }
27
+ }
app/modules/import-export/runners/revert/taxonomies.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Revert;
4
+
5
+ class Taxonomies extends Revert_Runner_Base {
6
+
7
+ public static function get_name() : string {
8
+ return 'taxonomies';
9
+ }
10
+
11
+ public function should_revert( array $data ) : bool {
12
+ return (
13
+ isset( $data['runners'] ) &&
14
+ array_key_exists( static::get_name(), $data['runners'] )
15
+ );
16
+ }
17
+
18
+ public function revert( array $data ) {
19
+ $taxonomies = get_taxonomies();
20
+
21
+ $terms = get_terms( [
22
+ 'taxonomy' => $taxonomies,
23
+ 'hide_empty' => false,
24
+ 'get' => 'all',
25
+ 'meta_query' => [
26
+ [
27
+ 'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID,
28
+ 'value' => $data['session_id'],
29
+ ],
30
+ ],
31
+ ] );
32
+
33
+ foreach ( $terms as $term ) {
34
+ wp_delete_term( $term->term_id, $term->taxonomy );
35
+ }
36
+ }
37
+ }
app/modules/import-export/runners/revert/templates.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Revert;
4
+
5
+ class Templates extends Revert_Runner_Base {
6
+ /*
7
+ * The implement of this runner is part of the Pro plugin.
8
+ */
9
+
10
+ public static function get_name() : string {
11
+ return 'templates';
12
+ }
13
+
14
+ public function should_revert( array $data ) : bool {
15
+ return false;
16
+ }
17
+
18
+ public function revert( array $data ) { }
19
+ }
app/modules/import-export/runners/revert/wp-content.php ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners\Revert;
4
+
5
+ use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
6
+
7
+ class Wp_Content extends Revert_Runner_Base {
8
+
9
+ public static function get_name() : string {
10
+ return 'wp-content';
11
+ }
12
+
13
+ public function should_revert( array $data ) : bool {
14
+ return (
15
+ isset( $data['runners'] ) &&
16
+ array_key_exists( static::get_name(), $data['runners'] )
17
+ );
18
+ }
19
+
20
+ public function revert( array $data ) {
21
+ $builtin_post_types = ImportExportUtils::get_builtin_wp_post_types();
22
+ $custom_post_types = $data['runners']['wp-content']['custom_post_types'] ?? [];
23
+
24
+ $post_types = array_merge( $builtin_post_types, $custom_post_types );
25
+
26
+ $query_args = [
27
+ 'post_type' => $post_types,
28
+ 'post_status' => 'any',
29
+ 'posts_per_page' => -1,
30
+ 'meta_query' => [
31
+ [
32
+ 'key' => static::META_KEY_ELEMENTOR_EDIT_MODE,
33
+ 'compare' => 'NOT EXISTS',
34
+ ],
35
+ [
36
+ 'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID,
37
+ 'value' => $data['session_id'],
38
+ ],
39
+ ],
40
+ ];
41
+
42
+ $query = new \WP_Query( $query_args );
43
+
44
+ foreach ( $query->posts as $post ) {
45
+ wp_delete_post( $post->ID, true );
46
+ }
47
+
48
+ /**
49
+ * Revert the nav menu terms.
50
+ * BC: The nav menu in new kits will be imported as part of the taxonomies, but old kits
51
+ * importing the nav menu terms as part from the wp-content import.
52
+ */
53
+ $this->revert_nav_menus( $data );
54
+ }
55
+
56
+ private function revert_nav_menus( array $data ) {
57
+ $terms = get_terms( [
58
+ 'taxonomy' => 'nav_menu',
59
+ 'hide_empty' => false,
60
+ 'get' => 'all',
61
+ 'meta_query' => [
62
+ [
63
+ 'key' => static::META_KEY_ELEMENTOR_IMPORT_SESSION_ID,
64
+ 'value' => $data['session_id'],
65
+ ],
66
+ ],
67
+ ] );
68
+
69
+ foreach ( $terms as $term ) {
70
+ wp_delete_term( $term->term_id, $term->taxonomy );
71
+ }
72
+ }
73
+ }
app/modules/import-export/runners/runner-interface.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport\Runners;
4
+
5
+ use Elementor\App\Modules\ImportExport\Module;
6
+
7
+ interface Runner_Interface {
8
+
9
+ const META_KEY_ELEMENTOR_IMPORT_SESSION_ID = Module::META_KEY_ELEMENTOR_IMPORT_SESSION_ID;
10
+
11
+ const META_KEY_ELEMENTOR_EDIT_MODE = Module::META_KEY_ELEMENTOR_EDIT_MODE;
12
+
13
+ /**
14
+ * Get the name of the runners, used to identify the runner.
15
+ * The name should be unique, unless you want to run over existing runner.
16
+ *
17
+ * @return string
18
+ */
19
+ public static function get_name() : string;
20
+ }
app/modules/import-export/usage.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport;
4
+
5
+ use Elementor\App\Modules\ImportExport\Processes\Revert;
6
+
7
+ if ( ! defined( 'ABSPATH' ) ) {
8
+ exit; // Exit if accessed directly
9
+ }
10
+
11
+ class Usage {
12
+
13
+ /**
14
+ * Register hooks.
15
+ *
16
+ * @return void
17
+ */
18
+ public function register() {
19
+ add_filter( 'elementor/tracker/send_tracking_data_params', function ( array $params ) {
20
+ $params['usages']['import_export']['revert'] = $this->get_revert_usage_data();
21
+
22
+ return $params;
23
+ } );
24
+ }
25
+
26
+ /**
27
+ * Get the Revert usage data.
28
+ *
29
+ * @return array
30
+ */
31
+ private function get_revert_usage_data() {
32
+ $revert_sessions = ( new Revert() )->get_revert_sessions();
33
+
34
+ $data = [];
35
+
36
+ foreach ( $revert_sessions as $revert_session ) {
37
+ $data[] = [
38
+ 'kit_name' => $revert_session['kit_name'],
39
+ 'source' => $revert_session['source'],
40
+ 'revert_timestamp' => (int) $revert_session['revert_timestamp'],
41
+ 'total_time' => ( (int) $revert_session['revert_timestamp'] - (int) $revert_session['import_timestamp'] ),
42
+ ];
43
+ }
44
+
45
+ return $data;
46
+ }
47
+ }
app/modules/import-export/utils.php ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Elementor\App\Modules\ImportExport;
4
+
5
+ use Elementor\Core\Utils\Str;
6
+ use Elementor\Modules\LandingPages\Module as Landing_Pages_Module;
7
+ use Elementor\Modules\System_Info\Reporters\Server;
8
+ use Elementor\TemplateLibrary\Source_Local;
9
+ use Elementor\Utils as ElementorUtils;
10
+
11
+ class Utils {
12
+
13
+ public static function read_json_file( $path ) {
14
+ if ( ! Str::ends_with( $path, '.json' ) ) {
15
+ $path .= '.json';
16
+ }
17
+
18
+ $file_content = ElementorUtils::file_get_contents( $path, true );
19
+
20
+ return $file_content ? json_decode( $file_content, true ) : [];
21
+ }
22
+
23
+ public static function map_old_new_post_ids( array $imported_data ) {
24
+ $result = [];
25
+
26
+ $result += $imported_data['templates']['succeed'] ?? [];
27
+
28
+ if ( isset( $imported_data['content'] ) ) {
29
+ foreach ( $imported_data['content'] as $post_type ) {
30
+ $result += $post_type['succeed'] ?? [];
31
+ }
32
+ }
33
+
34
+ if ( isset( $imported_data['wp-content'] ) ) {
35
+ foreach ( $imported_data['wp-content'] as $post_type ) {
36
+ $result += $post_type['succeed'] ?? [];
37
+ }
38
+ }
39
+
40
+ return $result;
41
+ }
42
+
43
+ public static function map_old_new_term_ids( array $imported_data ) {
44
+ $result = [];
45
+
46
+ if ( ! isset( $imported_data['taxonomies'] ) ) {
47
+ return $result;
48
+ }
49
+
50
+ foreach ( $imported_data['taxonomies'] as $post_type_taxonomies ) {
51
+ foreach ( $post_type_taxonomies as $taxonomy ) {
52
+ foreach ( $taxonomy as $term ) {
53
+ $result[ $term['old_id'] ] = $term['new_id'];
54
+ }
55
+ }
56
+ }
57
+
58
+ return $result;
59
+ }
60
+
61
+ public static function get_elementor_post_types() {
62
+ return [ 'post', 'page', 'e-landing-page' ];
63
+ }
64
+
65
+ public static function get_builtin_wp_post_types() {
66
+ return [ 'post', 'page', 'nav_menu_item' ];
67
+ }
68
+
69
+ public static function get_registered_cpt_names() {
70
+ $post_types = get_post_types( [
71
+ 'public' => true,
72
+ 'can_export' => true,
73
+ '_builtin' => false,
74
+ ] );
75
+
76
+ unset(
77
+ $post_types[ Landing_Pages_Module::CPT ],
78
+ $post_types[ Source_Local::CPT ]
79
+ );
80
+
81
+ return array_keys( $post_types );
82
+ }
83
+ }
{core/app → app}/modules/import-export/wp-cli.php RENAMED
@@ -1,12 +1,11 @@
1
  <?php
2
 
3
- namespace Elementor\Core\App\Modules\ImportExport;
4
 
5
  use Elementor\Core\Utils\Collection;
6
  use Elementor\Core\Utils\Plugins_Manager;
7
  use Elementor\Plugin;
8
- use Elementor\Core\App\Modules\KitLibrary\Connect\Kit_Library;
9
- use Elementor\Utils;
10
 
11
  if ( ! defined( 'ABSPATH' ) ) {
12
  exit; // Exit if accessed directly
@@ -14,6 +13,8 @@ if ( ! defined( 'ABSPATH' ) ) {
14
 
15
  class Wp_Cli extends \WP_CLI_Command {
16
 
 
 
17
  /**
18
  * Export a Kit
19
  *
@@ -39,20 +40,23 @@ class Wp_Cli extends \WP_CLI_Command {
39
 
40
  \WP_CLI::line( 'Kit export started.' );
41
 
42
- $export_settings = [
43
- 'include' => [ 'content', 'templates', 'settings' ],
44
- ];
45
-
46
  foreach ( $assoc_args as $key => $value ) {
47
- $import_settings[ $key ] = explode( ',', $value );
48
- }
 
49
 
50
- $export_settings = array_merge( $export_settings, $assoc_args );
 
51
 
52
  try {
53
- $exporter = new Export( $export_settings );
54
-
55
- $result = $exporter->run();
 
 
 
 
56
 
57
  rename( $result['file_name'], $args[0] );
58
  } catch ( \Error $error ) {
@@ -104,56 +108,64 @@ class Wp_Cli extends \WP_CLI_Command {
104
 
105
  \WP_CLI::line( 'Kit import started' );
106
 
107
- \WP_CLI::line( 'Extracting zip archive...' );
108
-
109
  $assoc_args = wp_parse_args( $assoc_args, [
110
  'sourceType' => 'local',
111
  ] );
112
 
113
  $url = null;
114
  $file_path = $args[0];
115
-
116
- if ( 'library' === $assoc_args['sourceType'] ) {
117
- $url = $this->get_url_from_library( $args[0] );
118
- } elseif ( 'remote' === $assoc_args['sourceType'] ) {
119
- $url = $args[0];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  }
121
 
122
  if ( 'enable' === $assoc_args['unfilteredFilesUpload'] ) {
123
- Plugin::$instance->uploads_manager->set_elementor_upload_state( true );
124
  Plugin::$instance->uploads_manager->enable_unfiltered_files_upload();
125
  }
126
 
127
- if ( $url ) {
128
- $file_path = $this->create_temp_file_from_url( $url );
129
- }
130
-
131
- $extraction_result = Plugin::$instance->uploads_manager->extract_and_validate_zip( $file_path, [ 'json', 'xml' ] );
132
-
133
- if ( is_wp_error( $extraction_result ) ) {
134
- \WP_CLI::error( $extraction_result->get_error_message() );
135
- }
136
-
137
- $import_settings = [
138
- 'include' => [ 'templates', 'content', 'settings' ],
139
- 'session' => $extraction_result['extraction_directory'],
140
- ];
141
-
142
  foreach ( $assoc_args as $key => $value ) {
 
 
 
 
143
  $import_settings[ $key ] = explode( ',', $value );
144
  }
145
 
146
- // Remove irrelevant settings from the $import_settings array
147
- $remove_irrelevant = [ 'sourceType', 'unfilteredFilesUpload' ];
148
- $import_settings = array_diff_key( $import_settings, array_flip( $remove_irrelevant ) );
149
-
150
  try {
151
  \WP_CLI::line( 'Importing data...' );
152
 
153
- $import = new Import( $import_settings );
 
 
 
 
 
 
 
 
 
 
 
154
 
155
- $manifest_data = $this->get_manifest_data( $import_settings['session'] );
156
- $manifest_data = $import->adapt_manifest_structure( $manifest_data );
157
 
158
  /**
159
  * Import Export Manifest Data
@@ -166,31 +178,43 @@ class Wp_Cli extends \WP_CLI_Command {
166
  */
167
  $manifest_data = apply_filters( 'elementor/import-export/wp-cli/manifest_data', $manifest_data );
168
 
169
- if ( isset( $manifest_data['plugins'] ) ) {
170
- $successfully_imported_plugins = $this->import_plugins( $manifest_data['plugins'] );
171
-
172
- \WP_CLI::line( 'Ready to use plugins: ' . $successfully_imported_plugins );
173
- }
174
-
175
- Plugin::$instance->app->get_component( 'import-export' )->import = $import;
176
-
177
- $import->run();
178
-
179
  \WP_CLI::line( 'Removing temp files...' );
180
 
181
- Plugin::$instance->uploads_manager->remove_file_or_dir( $import_settings['session'] );
182
-
183
- // The file was created from remote or library request and it should be removed.
184
  if ( $url ) {
185
- Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $file_path ) );
186
  }
187
 
188
  \WP_CLI::success( 'Kit imported successfully' );
189
  } catch ( \Error $error ) {
190
- Plugin::$instance->uploads_manager->remove_file_or_dir( $import_settings['session'] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
 
 
192
  \WP_CLI::error( $error->getMessage() );
193
  }
 
 
194
  }
195
 
196
  /**
@@ -227,6 +251,7 @@ class Wp_Cli extends \WP_CLI_Command {
227
  * @return string
228
  */
229
  private function create_temp_file_from_url( $url ) {
 
230
  $response = wp_remote_get( $url );
231
 
232
  if ( is_wp_error( $response ) ) {
@@ -237,30 +262,15 @@ class Wp_Cli extends \WP_CLI_Command {
237
  \WP_CLI::error( "Download file url: {$response['response']['message']}" );
238
  }
239
 
240
- return Plugin::$instance->uploads_manager->create_temp_file( $response['body'], 'kit.zip' );
241
- }
242
 
243
- /**
244
- * Helper to get the manifest data from the 'manifest.json' file.
245
- *
246
- * @param string $extraction_directory
247
- * @return array
248
- */
249
- private function get_manifest_data( $extraction_directory ) {
250
- $manifest_file_content = Utils::file_get_contents( $extraction_directory . 'manifest.json', true );
251
-
252
- if ( ! $manifest_file_content ) {
253
- \WP_CLI::error( 'Manifest not found' );
254
- }
255
 
256
- $manifest_data = json_decode( $manifest_file_content, true );
 
257
 
258
- // In case that the manifest content is not a valid JSON or empty.
259
- if ( ! $manifest_data ) {
260
- \WP_CLI::error( 'Manifest content is not valid json' );
261
- }
262
-
263
- return $manifest_data;
264
  }
265
 
266
  /**
@@ -271,39 +281,5 @@ class Wp_Cli extends \WP_CLI_Command {
271
  * @param array $plugins
272
  * @return string
273
  */
274
- private function import_plugins( $plugins ) {
275
- $plugins_collection = ( new Collection( $plugins ) )
276
- ->map( function ( $item ) {
277
- if ( ! $this->ends_with( $item['plugin'], '.php' ) ) {
278
- $item['plugin'] .= '.php';
279
- }
280
- return $item;
281
- } );
282
-
283
- $slugs = $plugins_collection
284
- ->map( function ( $item ) {
285
- return $item['plugin'];
286
- } )
287
- ->all();
288
-
289
- $plugins_manager = new Plugins_Manager();
290
-
291
- $install = $plugins_manager->install( $slugs );
292
- $activate = $plugins_manager->activate( $install['succeeded'] );
293
-
294
- $names = $plugins_collection
295
- ->filter( function ( $item ) use ( $activate ) {
296
- return in_array( $item['plugin'], $activate['succeeded'], true );
297
- } )
298
- ->map( function ( $item ) {
299
- return $item['name'];
300
- } )
301
- ->implode( ', ' );
302
-
303
- return $names;
304
- }
305
 
306
- private function ends_with( $haystack, $needle ) {
307
- return substr( $haystack, -strlen( $needle ) ) === $needle;
308
- }
309
  }
1
  <?php
2
 
3
+ namespace Elementor\App\Modules\ImportExport;
4
 
5
  use Elementor\Core\Utils\Collection;
6
  use Elementor\Core\Utils\Plugins_Manager;
7
  use Elementor\Plugin;
8
+ use Elementor\App\Modules\KitLibrary\Connect\Kit_Library;
 
9
 
10
  if ( ! defined( 'ABSPATH' ) ) {
11
  exit; // Exit if accessed directly
13
 
14
  class Wp_Cli extends \WP_CLI_Command {
15
 
16
+ const AVAILABLE_SETTINGS = [ 'include', 'overrideConditions', 'selectedCustomPostTypes', 'plugins' ];
17
+
18
  /**
19
  * Export a Kit
20
  *
40
 
41
  \WP_CLI::line( 'Kit export started.' );
42
 
43
+ $export_settings = [];
 
 
 
44
  foreach ( $assoc_args as $key => $value ) {
45
+ if ( ! in_array( $key, static::AVAILABLE_SETTINGS, true ) ) {
46
+ continue;
47
+ }
48
 
49
+ $export_settings[ $key ] = explode( ',', $value );
50
+ }
51
 
52
  try {
53
+ /**
54
+ * Running the export process through the import-export module so the export property in the module will be available to use.
55
+ *
56
+ * @type Module $import_export_module
57
+ */
58
+ $import_export_module = Plugin::$instance->app->get_component( 'import-export' );
59
+ $result = $import_export_module->export_kit( $export_settings );
60
 
61
  rename( $result['file_name'], $args[0] );
62
  } catch ( \Error $error ) {
108
 
109
  \WP_CLI::line( 'Kit import started' );
110
 
 
 
111
  $assoc_args = wp_parse_args( $assoc_args, [
112
  'sourceType' => 'local',
113
  ] );
114
 
115
  $url = null;
116
  $file_path = $args[0];
117
+ $import_settings = [];
118
+ $import_settings['referrer'] = Module::REFERRER_LOCAL;
119
+
120
+ switch ( $assoc_args['sourceType'] ) {
121
+ case 'library':
122
+ $url = $this->get_url_from_library( $file_path );
123
+ $zip_path = $this->create_temp_file_from_url( $url );
124
+ $import_settings['referrer'] = Module::REFERRER_KIT_LIBRARY;
125
+ break;
126
+
127
+ case 'remote':
128
+ $zip_path = $this->create_temp_file_from_url( $file_path );
129
+ break;
130
+
131
+ case 'local':
132
+ $zip_path = $file_path;
133
+ break;
134
+
135
+ default:
136
+ \WP_CLI::error( 'Unknown source type.' );
137
+ break;
138
  }
139
 
140
  if ( 'enable' === $assoc_args['unfilteredFilesUpload'] ) {
 
141
  Plugin::$instance->uploads_manager->enable_unfiltered_files_upload();
142
  }
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  foreach ( $assoc_args as $key => $value ) {
145
+ if ( ! in_array( $key, static::AVAILABLE_SETTINGS, true ) ) {
146
+ continue;
147
+ }
148
+
149
  $import_settings[ $key ] = explode( ',', $value );
150
  }
151
 
 
 
 
 
152
  try {
153
  \WP_CLI::line( 'Importing data...' );
154
 
155
+ /**
156
+ * Running the import process through the import-export module so the import property in the module will be available to use.
157
+ *
158
+ * @type Module $import_export_module
159
+ */
160
+ $import_export_module = Plugin::$instance->app->get_component( 'import-export' );
161
+
162
+ if ( ! $import_export_module ) {
163
+ \WP_CLI::error( 'Import Export module is not available.' );
164
+ }
165
+
166
+ $import = $import_export_module->import_kit( $zip_path, $import_settings );
167
 
168
+ $manifest_data = $import_export_module->import->get_manifest();
 
169
 
170
  /**
171
  * Import Export Manifest Data
178
  */
179
  $manifest_data = apply_filters( 'elementor/import-export/wp-cli/manifest_data', $manifest_data );
180
 
 
 
 
 
 
 
 
 
 
 
181
  \WP_CLI::line( 'Removing temp files...' );
182
 
183
+ // The file was created from remote or library request, it also should be removed.
 
 
184
  if ( $url ) {
185
+ Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $zip_path ) );
186
  }
187
 
188
  \WP_CLI::success( 'Kit imported successfully' );
189
  } catch ( \Error $error ) {
190
+ if ( $url ) {
191
+ Plugin::$instance->uploads_manager->remove_file_or_dir( dirname( $zip_path ) );
192
+ }
193
+
194
+ \WP_CLI::error( $error->getMessage() );
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Revert last imported kit.
200
+ */
201
+ public function revert() {
202
+ \WP_CLI::line( 'Kit revert started.' );
203
+
204
+ try {
205
+ /**
206
+ * Running the revert process through the import-export module so the revert property in the module will be available to use.
207
+ *
208
+ * @type Module $import_export_module
209
+ */
210
+ $import_export_module = Plugin::$instance->app->get_component( 'import-export' );
211
+ $import_export_module->revert_last_imported_kit();
212
 
213
+ } catch ( \Error $error ) {
214
  \WP_CLI::error( $error->getMessage() );
215
  }
216
+
217
+ \WP_CLI::success( 'Kit reverted successfully.' );
218
  }
219
 
220
  /**
251
  * @return string
252
  */
253
  private function create_temp_file_from_url( $url ) {
254
+ \WP_CLI::line( 'Extracting zip archive...' );
255
  $response = wp_remote_get( $url );
256
 
257
  if ( is_wp_error( $response ) ) {
262
  \WP_CLI::error( "Download file url: {$response['response']['message']}" );
263
  }
264
 
265
+ // Set the Request's state as an Elementor upload request, in order to support unfiltered file uploads.
266
+ Plugin::$instance->uploads_manager->set_elementor_upload_state( true );
267
 
268
+ $file = Plugin::$instance->uploads_manager->create_temp_file( $response['body'], 'kit.zip' );
 
 
 
 
 
 
 
 
 
 
 
269
 
270
+ // After the upload complete, set the elementor upload state back to false.
271
+ Plugin::$instance->uploads_manager->set_elementor_upload_state( false );
272
 
273
+ return $file;
 
 
 
 
 
274
  }
275
 
276
  /**
281
  * @param array $plugins
282
  * @return string
283
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
 
 
 
 
285
  }
app/modules/kit-library/assets/js/app.js ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Favorites from './pages/favorites/favorites';
2
+ import Index from './pages/index';
3
+ import Overview from './pages/overview/overview';
4
+ import Preview from './pages/preview/preview';
5
+ import { LastFilterProvider } from './context/last-filter-context';
6
+ import { QueryClientProvider, QueryClient } from 'react-query';
7
+ import { ReactQueryDevtools } from 'react-query/devtools';
8
+ import { Router } from '@reach/router';
9
+ import { SettingsProvider } from './context/settings-context';
10
+
11
+ const queryClient = new QueryClient( {
12
+ defaultOptions: {
13
+ queries: {
14
+ refetchOnWindowFocus: false,
15
+ retry: false,
16
+ staleTime: 1000 * 60 * 30, // 30 minutes
17
+ },
18
+ },
19
+ } );
20
+
21
+ export default function App() {
22
+ return (
23
+ <div className="e-kit-library">
24
+ <QueryClientProvider client={ queryClient }>
25
+ <SettingsProvider value={ elementorAppConfig[ 'kit-library' ] }>
26
+ <LastFilterProvider>
27
+ <Router>
28
+ <Index path="/" />
29
+ <Favorites path="/favorites" />
30
+ <Preview path="/preview/:id" />
31
+ <Overview path="/overview/:id" />
32
+ </Router>
33
+ </LastFilterProvider>
34
+ </SettingsProvider>
35
+ { elementorCommon.config.isElementorDebug && <ReactQueryDevtools initialIsOpen={ false } /> }
36
+ </QueryClientProvider>
37
+ </div>
38
+ );
39
+ }
app/modules/kit-library/assets/js/components/apply-kit-dialog.js ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useCallback } from 'react';
2
+ import { useNavigate } from '@reach/router';
3
+ import { Dialog } from '@elementor/app-ui';
4
+
5
+ export default function ApplyKitDialog( props ) {
6
+ const navigate = useNavigate();
7
+
8
+ const startImportProcess = useCallback( ( applyAll = false ) => {
9
+ let url = '/import/process' +
10
+ `?file_url=${ encodeURIComponent( props.downloadLink ) }` +
11
+ `&nonce=${ props.nonce }&referrer=kit-library`;
12
+
13
+ if ( applyAll ) {
14
+ url += '&action_type=apply-all';
15
+ }
16
+
17
+ navigate( url );
18
+ }, [ props.downloadLink, props.nonce ] );
19
+
20
+ return (
21
+ <Dialog
22
+ // Translators: %s is the kit name.
23
+ title={ __( 'Apply %s?', 'elementor' ).replace( '%s', props.title ) }
24
+ text={ <>
25
+ { __( 'You can use everything in this kit, or Customize to only include some items.', 'elementor' ) }
26
+ <br /><br />
27
+ { __( 'By applying the entire kit, you\'ll override any styles, settings or content already on your site.', 'elementor' ) }
28
+ </> }
29
+ approveButtonText={ __( 'Apply All', 'elementor' ) }
30
+ approveButtonColor="primary"
31
+ approveButtonOnClick={ () => startImportProcess( true ) }
32
+ dismissButtonText={ __( 'Customize', 'elementor' ) }
33
+ dismissButtonOnClick={ () => startImportProcess( false ) }
34
+ onClose={ props.onClose }
35
+ />
36
+ );
37
+ }
38
+
39
+ ApplyKitDialog.propTypes = {
40
+ downloadLink: PropTypes.string.isRequired,
41
+ nonce: PropTypes.string.isRequired,
42
+ onClose: PropTypes.func.isRequired,
43
+ title: PropTypes.string,
44
+ };
45
+
46
+ ApplyKitDialog.defaultProps = {
47
+ title: 'Kit',
48
+ };
app/modules/kit-library/assets/js/components/badge.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import './badge.scss';
2
+
3
+ export default function Badge( props ) {
4
+ return (
5
+ <span className={ `eps-badge eps-badge--${ props.variant } ${ props.className }` } style={ props.style }>
6
+ { props.children }
7
+ </span>
8
+ );
9
+ }
10
+
11
+ Badge.propTypes = {
12
+ children: PropTypes.node,
13
+ className: PropTypes.string,
14
+ style: PropTypes.object,
15
+ variant: PropTypes.oneOf( [ 'sm', 'md' ] ),
16
+ };
17
+
18
+ Badge.defaultProps = {
19
+ className: '',
20
+ style: {},
21
+ variant: 'md',
22
+ };
app/modules/kit-library/assets/js/components/badge.scss ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --eps-badge-background-color: #{$eps-theme-light};
3
+ }
4
+
5
+ .eps-theme-dark {
6
+ --eps-badge-background-color: #{$eps-dark-gray-600};
7
+ }
8
+
9
+ .eps-badge {
10
+ display: inline-block;
11
+ background: var(--eps-badge-background-color);
12
+ padding: 0 spacing(8);
13
+ line-height: type(line-height, lg);
14
+ box-shadow: shadow("1");
15
+ border-radius: 4px;
16
+ font-size: type(size, "12");
17
+
18
+ &--sm {
19
+ font-size: type(size, '10');
20
+ border-radius: 3px;
21
+ padding: 0 spacing(5);
22
+ line-height: type(line-height, base);
23
+ }
24
+ }
app/modules/kit-library/assets/js/components/collapse.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import './collapse.scss';
2
+
3
+ export default function Collapse( props ) {
4
+ // The state of the collapse managed by the parent component to let the parent control if the collapse is open or closed by default.
5
+ return (
6
+ <div
7
+ className={ `eps-collapse ${ props.className }` }
8
+ data-open={ props.isOpen || undefined /* Set `undefined` when 'isOpen' equals `false` to avoid showing the attr "data-open" */ }
9
+ >
10
+ <button
11
+ className="eps-collapse__title"
12
+ onClick={ () => {
13
+ props.onChange( ( value ) => ! value );
14
+ props.onClick?.( props.isOpen, props.title );
15
+ } }
16
+ >
17
+ <span>{ props.title }</span>
18
+ <i className="eicon-chevron-right eps-collapse__icon" />
19
+ </button>
20
+ <div className="eps-collapse__content">
21
+ { props.children }
22
+ </div>
23
+ </div>
24
+ );
25
+ }
26
+
27
+ Collapse.propTypes = {
28
+ isOpen: PropTypes.bool,
29
+ onChange: PropTypes.func,
30
+ className: PropTypes.string,
31
+ title: PropTypes.node,
32
+ onClick: PropTypes.func,
33
+ children: PropTypes.oneOfType( [
34
+ PropTypes.node,
35
+ PropTypes.arrayOf( PropTypes.node ),
36
+ ] ),
37
+ };
38
+
39
+ Collapse.defaultProps = {
40
+ className: '',
41
+ isOpen: false,
42
+ };
app/modules/kit-library/assets/js/components/collapse.scss ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .eps-collapse {
2
+ &__title {
3
+ cursor: pointer;
4
+ padding: spacing(5) 0;
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: space-between;
8
+ width: 100%;
9
+ background: transparent;
10
+ border: none;
11
+ color: inherit;
12
+
13
+ &:focus {
14
+ outline: none;
15
+ }
16
+ }
17
+
18
+ &__icon {
19
+ transition: all 150ms;
20
+ transform: rotate(getValueByDirection( 0deg, 180deg ));
21
+ }
22
+
23
+ &__content {
24
+ margin-top: spacing(10);
25
+ display: none;
26
+ }
27
+
28
+ &[data-open] &__content {
29
+ display: block;
30
+ }
31
+
32
+ &[data-open] &__icon {
33
+ transform: rotate(90deg);
34
+ }
35
+ }
app/modules/kit-library/assets/js/components/connect-dialog.js ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Dialog } from '@elementor/app-ui';
2
+ import { useSettingsContext } from '../context/settings-context';
3
+
4
+ const { useEffect, useRef } = React;
5
+
6
+ export default function ConnectDialog( props ) {
7
+ const { settings } = useSettingsContext();
8
+ const approveButtonRef = useRef();
9
+
10
+ useEffect( () => {
11
+ jQuery( approveButtonRef.current ).elementorConnect( {
12
+ success: ( e, data ) => props.onSuccess( data ),
13
+ error: () => props.onError( __( 'Unable to connect', 'elementor' ) ),
14
+ parseUrl: ( url ) => url.replace( '%%page%%', props.pageId ),
15
+ } );
16
+ }, [] );
17
+
18
+ return (
19
+ <Dialog
20
+ title={ __( 'Connect to Template Library', 'elementor' ) }
21
+ text={ __( 'Access this template and our entire library by creating a free personal account', 'elementor' ) }
22
+ approveButtonText={ __( 'Get Started', 'elementor' ) }
23
+ approveButtonUrl={ settings.library_connect_url }
24
+ approveButtonOnClick={ () => props.onClose() }
25
+ approveButtonColor="primary"
26
+ approveButtonRef={ approveButtonRef }
27
+ dismissButtonText={ __( 'Cancel', 'elementor-pro' ) }
28
+ dismissButtonOnClick={ () => props.onClose() }
29
+ onClose={ () => props.onClose() }
30
+ />
31
+ );
32
+ }
33
+
34
+ ConnectDialog.propTypes = {
35
+ onClose: PropTypes.func.isRequired,
36
+ onError: PropTypes.func.isRequired,
37
+ onSuccess: PropTypes.func.isRequired,
38
+ pageId: PropTypes.string,
39
+ };
app/modules/kit-library/assets/js/components/envato-promotion.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Text, Button } from '@elementor/app-ui';
2
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
3
+
4
+ import './envato-promotion.scss';
5
+
6
+ export default function EnvatoPromotion( props ) {
7
+ const eventTracking = ( command, eventType = 'click' ) => {
8
+ appsEventTrackingDispatch(
9
+ command,
10
+ {
11
+ page_source: 'home page',
12
+ element_position: 'library_bottom_promotion',
13
+ category: props.category && ( '/favorites' === props.category ? 'favorites' : 'all kits' ),
14
+ event_type: eventType,
15
+ },
16
+ );
17
+ };
18
+
19
+ return (
20
+ <Text className="e-kit-library-bottom-promotion" variant="xl">
21
+ { __( 'Looking for more Kits?', 'elementor' ) } { ' ' }
22
+ <Button
23
+ variant="underlined"
24
+ color="link"
25
+ url="https://go.elementor.com/app-envato-kits/"
26
+ target="_blank"
27
+ rel="noreferrer"
28
+ text={ __( 'Check out Elementor Template Kits on ThemeForest', 'elementor' ) }
29
+ onClick={ () => eventTracking( 'kit-library/check-kits-on-theme-forest' ) }
30
+ />
31
+ </Text>
32
+ );
33
+ }
34
+ EnvatoPromotion.propTypes = {
35
+ category: PropTypes.string,
36
+ };
app/modules/kit-library/assets/js/components/envato-promotion.scss ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $root: e-kit-library-bottom-promotion;
2
+
3
+ .#{$root} {
4
+ --e-kit-library-bottom-promotion-color: tints(600);
5
+ }
6
+
7
+ .eps-theme-dark {
8
+ .#{$root} {
9
+ --e-kit-library-bottom-promotion-color: dark-tints(400);
10
+ }
11
+ }
12
+
13
+ .#{$root} {
14
+ width: 100%;
15
+ text-align: center;
16
+ margin-top: spacing(30);
17
+ color: var(--e-kit-library-bottom-promotion-color);
18
+ }
app/modules/kit-library/assets/js/components/error-screen.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable jsx-a11y/alt-text */
2
+ import { Heading, Text, Grid, Button } from '@elementor/app-ui';
3
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
4
+
5
+ import './error-screen.scss';
6
+
7
+ export default function ErrorScreen( props ) {
8
+ const onClick = () => {
9
+ appsEventTrackingDispatch(
10
+ 'kit-library/go-back-to-view-kits',
11
+ {
12
+ page_source: 'home page',
13
+ element_position: 'empty state',
14
+ category: props.button.category && ( '/favorites' === props.button.category ? 'favorites' : 'all' ),
15
+ },
16
+ );
17
+ props.button.action();
18
+ };
19
+ return (
20
+
21
+ <Grid container alignItems="center" justify="center" direction="column" className="e-kit-library__error-screen">
22
+ <img src={ `${ elementorAppConfig.assets_url }images/no-search-results.svg` } />
23
+ <Heading
24
+ tag="h3"
25
+ variant="display-1"
26
+ className="e-kit-library__error-screen-title"
27
+ >
28
+ { props.title }
29
+ </Heading>
30
+ <Text variant="xl" className="e-kit-library__error-screen-description">
31
+ { props.description } { ' ' }
32
+ <Button
33
+ text={ props.button.text }
34
+ color="link"
35
+ onClick={ onClick }
36
+ url={ props.button.url }
37
+ target={ props.button.target }
38
+ />
39
+ </Text>
40
+ </Grid>
41
+ );
42
+ }
43
+
44
+ ErrorScreen.propTypes = {
45
+ title: PropTypes.string,
46
+ description: PropTypes.string,
47
+ button: PropTypes.shape( {
48
+ text: PropTypes.string,
49
+ action: PropTypes.func,
50
+ url: PropTypes.string,
51
+ target: PropTypes.string,
52
+ category: PropTypes.string,
53
+ } ),
54
+ };
app/modules/kit-library/assets/js/components/error-screen.scss ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-kit-library__error-screen {
2
+ margin-top: spacing(44);
3
+
4
+ &-title {
5
+ margin-top: spacing(44);
6
+ margin-bottom: 0;
7
+ }
8
+
9
+ &-description {
10
+ margin-top: spacing(24);
11
+ color: tints( 500 );
12
+ text-align: center;
13
+ max-width: 520px;
14
+ }
15
+ }
app/modules/kit-library/assets/js/components/favorites-actions.js ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useKitFavoritesMutations } from '../hooks/use-kit-favorites-mutations';
2
+ import { Button } from '@elementor/app-ui';
3
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
4
+
5
+ import './favorites-actions.scss';
6
+
7
+ export default function FavoritesActions( props ) {
8
+ const { addToFavorites, removeFromFavorites, isLoading } = useKitFavoritesMutations();
9
+
10
+ const loadingClasses = isLoading ? 'e-kit-library__kit-favorite-actions--loading' : '';
11
+ const eventTracking = ( kitName, source, action, gridLocation = null, searchTerm = null ) => {
12
+ appsEventTrackingDispatch(
13
+ 'kit-library/favorite-icon',
14
+ {
15
+ grid_location: gridLocation,
16
+ search_term: searchTerm,
17
+ kit_name: kitName,
18
+ page_source: source && ( '/' === source ? 'home page' : 'overview' ),
19
+ element_location: source && 'overview' === source ? 'app_sidebar' : null,
20
+ action,
21
+ },
22
+ );
23
+ };
24
+
25
+ return (
26
+ props.isFavorite
27
+ ? <Button
28
+ text={ __( 'Remove from Favorites', 'elementor' ) }
29
+ hideText={ true }
30
+ icon="eicon-heart"
31
+ className={ `e-kit-library__kit-favorite-actions e-kit-library__kit-favorite-actions--active ${ loadingClasses }` }
32
+ onClick={ () => {
33
+ // eslint-disable-next-line no-unused-expressions
34
+ ! isLoading && removeFromFavorites.mutate( props.id );
35
+ eventTracking( props?.name, props?.source, 'uncheck' );
36
+ } }
37
+ />
38
+ : <Button
39
+ text={ __( 'Add to Favorites', 'elementor' ) }
40
+ hideText={ true }
41
+ icon="eicon-heart-o"
42
+ className={ `e-kit-library__kit-favorite-actions ${ loadingClasses }` }
43
+ onClick={ () => {
44
+ // eslint-disable-next-line no-unused-expressions
45
+ ! isLoading && addToFavorites.mutate( props.id );
46
+ eventTracking( props?.name, props?.source, 'check', props?.index, props?.queryParams );
47
+ } }
48
+ />
49
+ );
50
+ }
51
+
52
+ FavoritesActions.propTypes = {
53
+ isFavorite: PropTypes.bool,
54
+ id: PropTypes.string,
55
+ name: PropTypes.string,
56
+ source: PropTypes.string,
57
+ index: PropTypes.number,
58
+ queryParams: PropTypes.string,
59
+ };
app/modules/kit-library/assets/js/components/favorites-actions.scss ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $root: e-kit-library__kit-favorite-actions;
2
+
3
+ .#{$root} {
4
+ padding: spacing(5);
5
+ transition: 0.3s all;
6
+ border-radius: 4px;
7
+
8
+ &--active {
9
+ color: theme-colors( danger );
10
+ }
11
+
12
+ &--loading {
13
+ opacity: 50%;
14
+ cursor: default;
15
+ }
16
+
17
+ &:hover {
18
+ background-color: rgba( theme-colors( danger ), $opacity-01 );
19
+ }
20
+ }
app/modules/kit-library/assets/js/components/filter-indication-text.js ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import useSelectedTaxonomies from '../hooks/use-selected-taxonomies';
2
+ import Badge from './badge';
3
+ import { sprintf, _n } from '@wordpress/i18n';
4
+ import { Text, Button, Grid } from '@elementor/app-ui';
5
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
6
+
7
+ import './filter-indication-text.scss';
8
+
9
+ export default function FilterIndicationText( props ) {
10
+ const selectedTaxonomies = useSelectedTaxonomies( props.queryParams.taxonomies );
11
+ const eventTracking = ( taxonomy, eventType = 'click' ) => {
12
+ appsEventTrackingDispatch(
13
+ 'kit-library/clear-filter',
14
+ {
15
+ tag: taxonomy,
16
+ page_source: 'home page',
17
+ event_type: eventType,
18
+ },
19
+ );
20
+ };
21
+
22
+ return (
23
+ <Grid container className="e-kit-library__filter-indication">
24
+ <Text className="e-kit-library__filter-indication-text">
25
+ {
26
+ // Translators: %s is the number of kits in the results
27
+ sprintf( _n( 'Showing %s result for', 'Showing %s results for', props.resultCount, 'elementor' ), ! props.resultCount ? __( 'no', 'elementor' ) : props.resultCount ) }
28
+ { ' ' }
29
+ { props.queryParams.search && `"${ props.queryParams.search }"` }
30
+ { ' ' }
31
+ { selectedTaxonomies.length > 0 && (
32
+ <>
33
+ { selectedTaxonomies.map( ( taxonomy ) => (
34
+ <Badge key={ taxonomy } className="e-kit-library__filter-indication-badge">
35
+ { taxonomy }
36
+ <Button
37
+ text={ __( 'Remove', 'elementor' ) }
38
+ hideText={ true }
39
+ icon="eicon-editor-close"
40
+ className="e-kit-library__filter-indication-badge-remove"
41
+ onClick={ () => {
42
+ eventTracking( taxonomy );
43
+ props.onRemoveTag( taxonomy );
44
+ } }
45
+ />
46
+ </Badge>
47
+ ) ) }
48
+ </>
49
+ ) }
50
+
51
+ </Text>
52
+ <Button
53
+ className="e-kit-library__filter-indication-button"
54
+ text={ __( 'Clear all', 'elementor' ) }
55
+ variant="underlined"
56
+ onClick={ () => {
57
+ eventTracking( 'all' );
58
+ props.onClear();
59
+ } }
60
+ />
61
+ </Grid>
62
+ );
63
+ }
64
+
65
+ FilterIndicationText.propTypes = {
66
+ queryParams: PropTypes.shape( {
67
+ search: PropTypes.string,
68
+ taxonomies: PropTypes.objectOf( PropTypes.arrayOf( PropTypes.string ) ),
69
+ favorite: PropTypes.bool,
70
+ } ),
71
+ resultCount: PropTypes.number.isRequired,
72
+ onClear: PropTypes.func.isRequired,
73
+ onRemoveTag: PropTypes.func.isRequired,
74
+ };
app/modules/kit-library/assets/js/components/filter-indication-text.scss ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-kit-library__filter-indication {
2
+ margin-top: spacing(24);
3
+ display: flex;
4
+ align-items: center;
5
+
6
+ &-text {
7
+ margin-bottom: spacing(0);
8
+ display: flex;
9
+ align-items: center;
10
+ }
11
+
12
+ &-badge {
13
+ @include margin-start(spacing(5));
14
+ display: flex;
15
+ align-items: center;
16
+ }
17
+
18
+ &-badge-remove {
19
+ @include margin-start(spacing(5));
20
+ font-size: type( size, "14" )
21
+ }
22
+
23
+ &-button {
24
+ @include margin-start(spacing(24));
25
+ }
26
+ }
app/modules/kit-library/assets/js/components/item-header.js ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ApplyKitDialog from './apply-kit-dialog';
2
+ import ConnectDialog from './connect-dialog';
3
+ import Header from './layout/header';
4
+ import HeaderBackButton from './layout/header-back-button';
5
+ import Kit from '../models/kit';
6
+ import useDownloadLinkMutation from '../hooks/use-download-link-mutation';
7
+ import useKitCallToAction, { TYPE_PROMOTION, TYPE_CONNECT } from '../hooks/use-kit-call-to-action';
8
+ import { Dialog } from '@elementor/app-ui';
9
+ import { useMemo, useState } from 'react';
10
+ import { useSettingsContext } from '../context/settings-context';
11
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
12
+
13
+ import './item-header.scss';
14
+
15
+ /**
16
+ * Returns the right call to action button.
17
+ *
18
+ * @param {Kit} model
19
+ * @param {Object} root0
20
+ * @param {Function} root0.apply
21
+ * @param {Function} root0.onConnect
22
+ * @param {Function} root0.onClick
23
+ * @param {boolean} root0.isApplyLoading
24
+ * @return {Object} result
25
+ */
26
+ function useKitCallToActionButton( model, { apply, isApplyLoading, onConnect, onClick } ) {
27
+ const [ type, { subscriptionPlan } ] = useKitCallToAction( model.accessLevel );
28
+
29
+ return useMemo( () => {
30
+ if ( type === TYPE_CONNECT ) {
31
+ return {
32
+ id: 'connect',
33
+ text: __( 'Apply Kit', 'elementor' ), // The label is Apply kit but the this is connect button
34
+ hideText: false,
35
+ variant: 'contained',
36
+ color: 'primary',
37
+ size: 'sm',
38
+ onClick: ( e ) => {
39
+ onConnect( e );
40
+ onClick?.( e );
41
+ },
42
+ includeHeaderBtnClass: false,
43
+ };
44
+ }
45
+
46
+ if ( type === TYPE_PROMOTION && subscriptionPlan ) {
47
+ const getButtonURL = () => {
48
+ let url = subscriptionPlan.promotion_url;
49
+
50
+ if ( model.title ) {
51
+ // Remove special characters, replace spaces with '-' and convert url kit name to lowercase.
52
+ const cleanTitle = model.title.replace( /\s/g, '-' ).replace( /[^\w-]/g, '' ).toLowerCase();
53
+ url += `&utm_term=${ cleanTitle }`;
54
+ }
55
+
56
+ if ( model.id ) {
57
+ url += `&utm_content=${ model.id }`;
58
+ }
59
+
60
+ return url;
61
+ };
62
+
63
+ return {
64
+ id: 'promotion',
65
+ // Translators: %s is the subscription plan name.
66
+ text: __( 'Go %s', 'elementor' ).replace( '%s', subscriptionPlan.label ),
67
+ hideText: false,
68
+ variant: 'contained',
69
+ color: 'cta',
70
+ size: 'sm',
71
+ url: getButtonURL(),
72
+ target: '_blank',
73
+ includeHeaderBtnClass: false,
74
+ };
75
+ }
76
+
77
+ return {
78
+ id: 'apply',
79
+ text: __( 'Apply Kit', 'elementor' ),
80
+ className: 'e-kit-library__apply-button',
81
+ icon: isApplyLoading ? 'eicon-loading eicon-animation-spin' : '',
82
+ hideText: false,
83
+ variant: 'contained',
84
+ color: isApplyLoading ? 'disabled' : 'primary',
85
+ size: 'sm',
86
+ onClick: ( e ) => {
87
+ if ( ! isApplyLoading ) {
88
+ apply( e );
89
+ }
90
+
91
+ onClick?.( e );
92
+ },
93
+ includeHeaderBtnClass: false,
94
+ };
95
+ }, [ type, subscriptionPlan, isApplyLoading, apply ] );
96
+ }
97
+
98
+ export default function ItemHeader( props ) {
99
+ const { updateSettings } = useSettingsContext();
100
+
101
+ const [ isConnectDialogOpen, setIsConnectDialogOpen ] = useState( false );
102
+ const [ downloadLinkData, setDownloadLinkData ] = useState( null );
103
+ const [ error, setError ] = useState( false );
104
+ const kitData = {
105
+ kitName: props.model.title,
106
+ pageId: props.pageId,
107
+ };
108
+ const { mutate: apply, isLoading: isApplyLoading } = useDownloadLinkMutation(
109
+ props.model,
110
+ {
111
+ onSuccess: ( { data } ) => setDownloadLinkData( data ),
112
+ onError: ( errorResponse ) => {
113
+ if ( 401 === errorResponse.code ) {
114
+ elementorCommon.config.library_connect.is_connected = false;
115
+ elementorCommon.config.library_connect.current_access_level = 0;
116
+
117
+ updateSettings( {
118
+ is_library_connected: false,
119
+ access_level: 0,
120
+ } );
121
+
122
+ setIsConnectDialogOpen( true );
123
+
124
+ return;
125
+ }
126
+
127
+ setError( {
128
+ code: errorResponse.code,
129
+ message: __( 'Something went wrong.', 'elementor' ),
130
+ } );
131
+ },
132
+ },
133
+ );
134
+
135
+ const applyButton = useKitCallToActionButton( props.model, {
136
+ onConnect: () => setIsConnectDialogOpen( true ),
137
+ apply,
138
+ isApplyLoading,
139
+ onClick: () => {
140
+ return appsEventTrackingDispatch(
141
+ 'kit-library/apply-kit',
142
+ {
143
+ kit_name: props.model.title,
144
+ element_position: 'app_header',
145
+ page_source: props.pageId,
146
+ event_type: 'click',
147
+ },
148
+ );
149
+ },
150
+ } );
151
+
152
+ const buttons = useMemo( () => [ applyButton, ...props.buttons ], [ props.buttons, applyButton ] );
153
+
154
+ return (
155
+ <>
156
+ {
157
+ error && (
158
+ <Dialog
159
+ title={ error.message }
160
+ text={ __( 'Nothing to worry about, just try again. If the problem continues, head over to the Help Center.', 'elementor' ) }
161
+ approveButtonText={ __( 'Learn More', 'elementor' ) }
162
+ approveButtonColor="link"
163
+ approveButtonUrl="http://go.elementor.com/app-kit-library-error/"
164
+ approveButtonOnClick={ () => setError( false ) }
165
+ dismissButtonText={ __( 'Got it', 'elementor' ) }
166
+ dismissButtonOnClick={ () => setError( false ) }
167
+ onClose={ () => setError( false ) }
168
+ />
169
+ )
170
+ }
171
+ {
172
+ downloadLinkData && <ApplyKitDialog
173
+ downloadLink={ downloadLinkData.data.download_link }
174
+ nonce={ downloadLinkData.meta.nonce }
175
+ onClose={ () => setDownloadLinkData( null ) }
176
+ />
177
+ }
178
+ {
179
+ isConnectDialogOpen && <ConnectDialog
180
+ pageId={ props.pageId }
181
+ onClose={ () => setIsConnectDialogOpen( false ) }
182
+ onSuccess={ ( data ) => {
183
+ const accessLevel = data.kits_access_level || data.access_level || 0;
184
+
185
+ elementorCommon.config.library_connect.is_connected = true;
186
+ elementorCommon.config.library_connect.current_access_level = accessLevel;
187
+
188
+ updateSettings( {
189
+ is_library_connected: true,
190
+ access_level: accessLevel, // BC: Check for 'access_level' prop
191
+ } );
192
+
193
+ if ( data.access_level < props.model.accessLevel ) {
194
+ return;
195
+ }
196
+
197
+ apply();
198
+ } }
199
+ onError={ ( message ) => setError( { message } ) }
200
+ />
201
+ }
202
+ <Header
203
+ startColumn={ <HeaderBackButton { ...kitData } /> }
204
+ centerColumn={ props.centerColumn }
205
+ buttons={ buttons }
206
+ { ...kitData }
207
+ />
208
+ </>
209
+ );
210
+ }
211
+
212
+ ItemHeader.propTypes = {
213
+ model: PropTypes.instanceOf( Kit ).isRequired,
214
+ centerColumn: PropTypes.node,
215
+ buttons: PropTypes.arrayOf( PropTypes.object ),
216
+ pageId: PropTypes.string,
217
+ };
app/modules/kit-library/assets/js/components/item-header.scss ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #eps-app-header-btn-apply,
2
+ #eps-app-header-btn-promotion,
3
+ #eps-app-header-btn-connect {
4
+ @include margin-start(spacing(10));
5
+ @include margin-end(spacing(10));
6
+ }
7
+
8
+ .e-kit-library__apply-button {
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ gap: spacing( 5 );
13
+ }
app/modules/kit-library/assets/js/components/kit-list-item.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Badge from './badge';
2
+ import FavoritesActions from '../components/favorites-actions';
3
+ import Kit from '../models/kit';
4
+ import useKitCallToAction, { TYPE_PROMOTION } from '../hooks/use-kit-call-to-action';
5
+ import { Card, CardHeader, CardBody, Heading, CardImage, CardOverlay, Grid, Button } from '@elementor/app-ui';
6
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
7
+
8
+ import './kit-list-item.scss';
9
+
10
+ const KitListItem = ( props ) => {
11
+ const [ type, { subscriptionPlan } ] = useKitCallToAction( props.model.accessLevel );
12
+
13
+ const eventTracking = ( command ) => {
14
+ appsEventTrackingDispatch(
15
+ command,
16
+ {
17
+ kit_name: props.model.title,
18
+ grid_location: props.index,
19
+ search_term: props.queryParams,
20
+ page_source: props.source && '/' === props.source ? 'all kits' : 'favorites',
21
+ },
22
+ );
23
+ };
24
+
25
+ return (
26
+ <Card className="e-kit-library__kit-item">
27
+ <CardHeader>
28
+ <Heading
29
+ tag="h3"
30
+ title={ props.model.title }
31
+ variant="h5"
32
+ className="eps-card__headline"
33
+ >
34
+ { props.model.title }
35
+ </Heading>
36
+ <FavoritesActions
37
+ id={ props.model.id }
38
+ isFavorite={ props.model.isFavorite }
39
+ index={ props.index }
40
+ name={ props.model.title }
41
+ queryParams={ props.queryParams }
42
+ source={ props.source }
43
+ />
44
+ </CardHeader>
45
+ <CardBody>
46
+ <CardImage alt={ props.model.title } src={ props.model.thumbnailUrl || '' }>
47
+ {
48
+ subscriptionPlan?.label &&
49
+ <Badge
50
+ variant="sm"
51
+ className="e-kit-library__kit-item-subscription-plan-badge"
52
+ style={ { backgroundColor: subscriptionPlan.color } }
53
+ >
54
+ { subscriptionPlan.label }
55
+ </Badge>
56
+ }
57
+ <CardOverlay>
58
+ <Grid container direction="column" className="e-kit-library__kit-item-overlay">
59
+ <Button
60
+ className="e-kit-library__kit-item-overlay-overview-button"
61
+ text={ __( 'View Demo', 'elementor' ) }
62
+ icon="eicon-preview-medium"
63
+ url={ `/kit-library/preview/${ props.model.id }` }
64
+ onClick={ () => eventTracking( 'kit-library/check-out-kit' ) }
65
+ />
66
+ {
67
+ type === TYPE_PROMOTION && subscriptionPlan?.label && <Button
68
+ className="e-kit-library__kit-item-overlay-promotion-button"
69
+ text={ `Go ${ subscriptionPlan.label }` }
70
+ icon="eicon-external-link-square"
71
+ url={ subscriptionPlan.promotion_url }
72
+ target="_blank"
73
+ />
74
+ }
75
+ </Grid>
76
+ </CardOverlay>
77
+ </CardImage>
78
+ </CardBody>
79
+ </Card>
80
+ );
81
+ };
82
+
83
+ KitListItem.propTypes = {
84
+ model: PropTypes.instanceOf( Kit ).isRequired,
85
+ index: PropTypes.number,
86
+ queryParams: PropTypes.string,
87
+ source: PropTypes.string,
88
+ };
89
+
90
+ export default React.memo( KitListItem );
app/modules/kit-library/assets/js/components/kit-list-item.scss ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $root: e-kit-library__kit-item;
2
+
3
+ .#{$root} {
4
+ --e-kit-library-kit-item-overlay-promotion-button-background-color: #{tints(100)};
5
+ }
6
+
7
+ .eps-theme-dark {
8
+ .#{$root} {
9
+ --e-kit-library-kit-item-overlay-promotion-button-background-color: #{dark-tints(600)};
10
+ }
11
+ }
12
+
13
+ .#{$root} {
14
+ &-overlay {
15
+ height: 100%;
16
+
17
+ & > *:first-child {
18
+ flex: 1;
19
+ }
20
+ }
21
+
22
+ &-overlay-overview-button {
23
+ display: flex;
24
+ flex-direction: column;
25
+ align-items: center;
26
+ justify-content: center;
27
+ color: white;
28
+ height: 100%;
29
+ width: 100%;
30
+
31
+ & > i {
32
+ font-size: 2rem;
33
+ margin-bottom: 5px;
34
+ }
35
+
36
+ & > span {
37
+ font-size: 0.9rem;
38
+ }
39
+ }
40
+
41
+ &-overlay-promotion-button {
42
+ display: flex;
43
+ width: 100%;
44
+ background: white;
45
+ align-items: center;
46
+ justify-content: center;
47
+ padding: 10px;
48
+ font-size: 13px;
49
+ color: theme-colors( cta );
50
+ background: var(--e-kit-library-kit-item-overlay-promotion-button-background-color);
51
+
52
+ & > * {
53
+ margin: 0 3px;
54
+ }
55
+ }
56
+
57
+ &-subscription-plan-badge {
58
+ position: absolute;
59
+ top: 0;
60
+ right: 0;
61
+ margin: spacing(5);
62
+ color: theme-colors( light );
63
+ text-transform: uppercase;
64
+ }
65
+ }
app/modules/kit-library/assets/js/components/kit-list.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useLocation } from '@reach/router';
2
+ import Kit from '../models/kit';
3
+ import KitListItem from './kit-list-item';
4
+ import NewPageKitListItem from '../../../../onboarding/assets/js/components/new-page-kit-list-item';
5
+ import { CssGrid } from '@elementor/app-ui';
6
+
7
+ export default function KitList( props ) {
8
+ const location = useLocation();
9
+
10
+ const referrer = new URLSearchParams( location.pathname.split( '?' )?.[ 1 ] ).get( 'referrer' );
11
+
12
+ return (
13
+ <CssGrid spacing={ 24 } colMinWidth={ 290 }>
14
+ {
15
+ 'onboarding' === referrer &&
16
+ <NewPageKitListItem />
17
+ }
18
+ {
19
+ props.data.map( ( model, index ) => (
20
+ // The + 1 was added in order to start the map.index from 1 and not from 0.
21
+ <KitListItem key={ model.id } model={ model } index={ index + 1 } queryParams={ props.queryParams?.search } source={ props.source } />
22
+ ) )
23
+ }
24
+ </CssGrid>
25
+ );
26
+ }
27
+
28
+ KitList.propTypes = {
29
+ data: PropTypes.arrayOf( PropTypes.instanceOf( Kit ) ),
30
+ queryParams: PropTypes.shape( {
31
+ search: PropTypes.string,
32
+ } ),
33
+ source: PropTypes.string,
34
+ };
app/modules/kit-library/assets/js/components/layout/header-back-button.js ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Button } from '@elementor/app-ui';
2
+ import { useLastFilterContext } from '../../context/last-filter-context';
3
+ import { useNavigate } from '@reach/router';
4
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
5
+
6
+ import './header-back-button.scss';
7
+
8
+ export default function HeaderBackButton( props ) {
9
+ const navigate = useNavigate(),
10
+ { lastFilter } = useLastFilterContext(),
11
+ eventTracking = ( command, eventType = 'click' ) => {
12
+ appsEventTrackingDispatch(
13
+ command,
14
+ {
15
+ page_source: props.pageId,
16
+ kit_name: props.kitName,
17
+ element_position: 'app_header',
18
+ event_type: eventType,
19
+ },
20
+ );
21
+ };
22
+ return (
23
+ <div className="e-kit-library__header-back-container">
24
+ <Button
25
+ className="e-kit-library__header-back"
26
+ icon="eicon-chevron-left"
27
+ text={ __( 'Back to Library', 'elementor' ) }
28
+ onClick={ () => {
29
+ eventTracking( 'kit-library/back-to-library' );
30
+ navigate( wp.url.addQueryArgs( '/kit-library', lastFilter ) );
31
+ } }
32
+ />
33
+ </div>
34
+ );
35
+ }
36
+
37
+ HeaderBackButton.propTypes = {
38
+ pageId: PropTypes.string.isRequired,
39
+ kitName: PropTypes.string.isRequired,
40
+ };
app/modules/kit-library/assets/js/components/layout/header-back-button.scss ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --e-kit-library-header-back-border: #{1px solid $eps-border-color};
3
+ --e-kit-library-header-back-color: #{tints(500)};
4
+ }
5
+
6
+ .eps-theme-dark {
7
+ --e-kit-library-header-back-border: #{1px solid $eps-dark-gray-400};
8
+ --e-kit-library-header-back-color: #{dark-tints(200)};
9
+ }
10
+
11
+ .e-kit-library__header-back {
12
+ color: var(--e-kit-library-header-back-color);
13
+ @include padding-end( spacing( 20 ) );
14
+ @include padding-start( spacing( 5 ) );
15
+ display: inline-flex;
16
+ justify-content: center;
17
+ align-items: center;
18
+ height: 100%;
19
+ @include border-end( var(--e-kit-library-header-back-border) );
20
+
21
+ &-container {
22
+ flex: 1;
23
+ height: 100%;
24
+ }
25
+
26
+ .#{$eps-prefix}icon {
27
+ transform: getValueByDirection(rotate(0deg), rotate(180deg));
28
+ }
29
+ }
app/modules/kit-library/assets/js/components/layout/header.js ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Grid } from '@elementor/app-ui';
2
+ import HeaderButtons from '../../../../../../assets/js/layout/header-buttons';
3
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
4
+
5
+ export default function Header( props ) {
6
+ const eventTracking = ( command, source = 'home page', kitName = null, eventType = 'click' ) => appsEventTrackingDispatch(
7
+ command,
8
+ {
9
+ page_source: source,
10
+ element_position: 'app_header',
11
+ kit_name: kitName,
12
+ event_type: eventType,
13
+ },
14
+ ),
15
+ onClose = () => {
16
+ eventTracking( 'kit-library/close', props?.pageId, props?.kitName );
17
+ window.top.location = elementorAppConfig.admin_url;
18
+ };
19
+
20
+ return (
21
+ <Grid container alignItems="center" justify="space-between" className="eps-app__header">
22
+ { props.startColumn || <a
23
+ className="eps-app__logo-title-wrapper"
24
+ href="#/kit-library"
25
+ onClick={ () => eventTracking( 'kit-library/logo' ) }
26
+ >
27
+ <i className="eps-app__logo eicon-elementor" />
28
+ <h1 className="eps-app__title">{ __( 'Kit Library', 'elementor' ) }</h1>
29
+ </a> }
30
+ { props.centerColumn || <span /> }
31
+ { props.endColumn || <div style={ { flex: 1 } }>
32
+ <HeaderButtons buttons={ props.buttons } onClose={ onClose } />
33
+ </div> }
34
+ </Grid>
35
+ );
36
+ }
37
+
38
+ Header.propTypes = {
39
+ startColumn: PropTypes.node,
40
+ endColumn: PropTypes.node,
41
+ centerColumn: PropTypes.node,
42
+ buttons: PropTypes.arrayOf( PropTypes.object ),
43
+ kitName: PropTypes.string,
44
+ pageId: PropTypes.string,
45
+ };
app/modules/kit-library/assets/js/components/layout/index.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Sidebar from '../../../../../../assets/js/layout/sidebar';
2
+
3
+ export default function Index( props ) {
4
+ return (
5
+ <div className="eps-app__lightbox">
6
+ <div className="eps-app">
7
+ { props.header }
8
+ <div className="eps-app__main">
9
+ {
10
+ props.sidebar &&
11
+ <Sidebar>
12
+ { props.sidebar }
13
+ </Sidebar>
14
+ }
15
+ { props.children }
16
+ </div>
17
+ </div>
18
+ </div>
19
+ );
20
+ }
21
+
22
+ Index.propTypes = {
23
+ header: PropTypes.node,
24
+ sidebar: PropTypes.node,
25
+ children: PropTypes.node,
26
+ };
app/modules/kit-library/assets/js/components/page-loader.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Icon } from '@elementor/app-ui';
2
+
3
+ import './page-loader.scss';
4
+
5
+ export default function PageLoader( props ) {
6
+ return (
7
+ <div className={ `e-kit-library__page-loader ${ props.className }` }>
8
+ <Icon className="eicon-loading eicon-animation-spin" />
9
+ </div>
10
+ );
11
+ }
12
+
13
+ PageLoader.propTypes = {
14
+ className: PropTypes.string,
15
+ };
16
+
17
+ PageLoader.defaultProps = {
18
+ className: '',
19
+ };
app/modules/kit-library/assets/js/components/page-loader.scss ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ .e-kit-library__page-loader {
2
+ width: 100%;
3
+ height: 100%;
4
+ display: grid;
5
+ place-items: center;
6
+ font-size: type( size, '30' );
7
+ color: tints( 500 );
8
+ }
app/modules/kit-library/assets/js/components/search-input.js ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import useDebouncedCallback from '../hooks/use-debounced-callback';
2
+ import { Icon, Button } from '@elementor/app-ui';
3
+ import { useState, useEffect } from 'react';
4
+
5
+ import './search-input.scss';
6
+
7
+ export default function SearchInput( props ) {
8
+ const [ localValue, setLocalValue ] = useState( props.value || '' );
9
+ const debouncedOnChange = useDebouncedCallback( ( value ) => props.onChange( value ), props.debounceTimeout );
10
+
11
+ useEffect( () => {
12
+ if ( props.value !== localValue ) {
13
+ setLocalValue( props.value );
14
+ }
15
+ }, [ props.value ] );
16
+
17
+ return (
18
+ <div className={ `eps-search-input__container ${ props.className }` }>
19
+ <input
20
+ className={ `eps-search-input eps-search-input--${ props.size }` }
21
+ placeholder={ props.placeholder }
22
+ value={ localValue }
23
+ onChange={ ( e ) => {
24
+ setLocalValue( e.target.value );
25
+ debouncedOnChange( e.target.value );
26
+ } }
27
+ />
28
+ <Icon className={ `eicon-search-bold eps-search-input__icon eps-search-input__icon--${ props.size }` } />
29
+ {
30
+ props.value &&
31
+ <Button
32
+ text={ __( 'Clear', 'elementor' ) }
33
+ hideText={ true }
34
+ className={ `eicon-close-circle eps-search-input__clear-icon eps-search-input__clear-icon--${ props.size }` }
35
+ onClick={ () => props.onChange( '' ) }
36
+ />
37
+ }
38
+ </div>
39
+ );
40
+ }
41
+
42
+ SearchInput.propTypes = {
43
+ placeholder: PropTypes.string,
44
+ value: PropTypes.string.isRequired,
45
+ onChange: PropTypes.func.isRequired,
46
+ className: PropTypes.string,
47
+ size: PropTypes.oneOf( [ 'md', 'sm' ] ),
48
+ debounceTimeout: PropTypes.number,
49
+ };
50
+
51
+ SearchInput.defaultProps = {
52
+ className: '',
53
+ size: 'md',
54
+ debounceTimeout: 300,
55
+ };
app/modules/kit-library/assets/js/components/search-input.scss ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $root: eps-search-input;
2
+
3
+ .#{$root} {
4
+ --eps-search-input-background-color: #{tints(100)};
5
+ --eps-search-input-background-color-focus: #{theme-colors(light)};
6
+ --eps-search-input-color: #{tints(700)};
7
+ --eps-search-input-placeholder-color: #{tints(500)};
8
+ }
9
+
10
+ .eps-theme-dark {
11
+ .#{$root} {
12
+ --eps-search-input-background-color: #{$eps-dark-gray-600};
13
+ --eps-search-input-background-color-focus: #{$eps-dark-gray-500};
14
+ --eps-search-input-color: #{$eps-dark-gray-200};
15
+ --eps-search-input-placeholder-color: #{dark-tints(200)};
16
+ }
17
+ }
18
+
19
+ .eps-search-input {
20
+ width: 100%;
21
+ font-size: type(size, "15");
22
+ padding: spacing(10) spacing(44);
23
+ border: none;
24
+ background: var(--eps-search-input-background-color);
25
+ outline: none;
26
+ color: var(--eps-search-input-color);
27
+ line-height: type( line-height, 'flat' );
28
+ height: spacing(44);
29
+
30
+ &--sm {
31
+ font-size: type(size, "13");
32
+ padding: spacing(8) spacing(30);
33
+ }
34
+
35
+ &:focus {
36
+ background: var(--eps-search-input-background-color-focus);
37
+ }
38
+
39
+ &::placeholder {
40
+ color: var(--eps-search-input-placeholder-color);
41
+ font-style: italic;
42
+ }
43
+
44
+ &__container {
45
+ position: relative;
46
+ }
47
+
48
+ &__icon {
49
+ font-size: type(size, "20");
50
+ padding: spacing(10);
51
+ color: tints(500);
52
+ position: absolute;
53
+ top: 0;
54
+ inset-inline-start: 0;
55
+ height: 100%;
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+
60
+ &--sm {
61
+ font-size: type(size, "12");
62
+ }
63
+ }
64
+
65
+ &__clear-icon {
66
+ font-size: type(size, "14");
67
+ padding: spacing(10);
68
+ color: tints(500);
69
+ position: absolute;
70
+ top: 0;
71
+ @include end(0);
72
+ height: 100%;
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+
77
+ &--sm {
78
+ font-size: type(size, "11");
79
+ }
80
+ }
81
+ }
app/modules/kit-library/assets/js/components/sort-select.js ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Select, Button } from '@elementor/app-ui';
2
+ import { useState, useEffect } from 'react';
3
+
4
+ import './sort-select.scss';
5
+
6
+ export default function SortSelect( props ) {
7
+ const getSelectedOptionDetails = ( value ) => {
8
+ return props.options.find( ( option ) => option.value === value );
9
+ };
10
+
11
+ const [ selectedSortBy, setSelectedSortBy ] = useState( getSelectedOptionDetails( props.value.by ) );
12
+
13
+ useEffect( () => {
14
+ props.onChange( { by: selectedSortBy.value, direction: selectedSortBy.defaultOrder ?? props.value.direction } );
15
+ }, [ selectedSortBy ] );
16
+
17
+ return (
18
+ <div className="eps-sort-select">
19
+ <div className="eps-sort-select__select-wrapper">
20
+ <Select
21
+ options={ props.options }
22
+ value={ props.value.by }
23
+ onChange={ ( e ) => {
24
+ const value = e.target.value;
25
+ setSelectedSortBy( getSelectedOptionDetails( value ) );
26
+ props.onChangeSortValue?.( value );
27
+ } }
28
+ className="eps-sort-select__select"
29
+ onClick={ () => {
30
+ props.onChange( {
31
+ by: props.value.by,
32
+ direction: props.value.direction,
33
+ } );
34
+ props.onSortSelectOpen?.();
35
+ } }
36
+ />
37
+ </div>
38
+ {
39
+ ! selectedSortBy.orderDisabled &&
40
+ <Button
41
+ text={ 'asc' === props.value.direction ? __( 'Sort Descending', 'elementor' ) : __( 'Sort Ascending', 'elementor' ) }
42
+ hideText={ true }
43
+ icon={ 'asc' === props.value.direction ? 'eicon-arrow-up' : 'eicon-arrow-down' }
44
+ className="eps-sort-select__button"
45
+ onClick={ () => {
46
+ const direction = props.value.direction && 'asc' === props.value.direction ? 'desc' : 'asc';
47
+ if ( props.onChangeSortDirection ) {
48
+ props.onChangeSortDirection( direction );
49
+ }
50
+ props.onChange( {
51
+ by: props.value.by,
52
+ direction,
53
+ } );
54
+ } }
55
+ />
56
+ }
57
+ </div>
58
+ );
59
+ }
60
+
61
+ SortSelect.propTypes = {
62
+ options: PropTypes.arrayOf( PropTypes.shape( {
63
+ label: PropTypes.string.isRequired,
64
+ value: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number ] ).isRequired,
65
+ } ) ).isRequired,
66
+ value: PropTypes.shape( {
67
+ direction: PropTypes.oneOf( [ 'asc', 'desc' ] ).isRequired,
68
+ by: PropTypes.string.isRequired,
69
+ } ).isRequired,
70
+ onChange: PropTypes.func.isRequired,
71
+ onChangeSortValue: PropTypes.func,
72
+ onSortSelectOpen: PropTypes.func,
73
+ onChangeSortDirection: PropTypes.func,
74
+ };
app/modules/kit-library/assets/js/components/sort-select.scss ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --eps-sort-select-select-background-color: #{tints(100)};
3
+ --eps-sort-select-select-color: #{tints(700)};
4
+ --eps-sort-select-button-background-color: #{tints(100)};
5
+ --eps-sort-select-button-border: #{1px solid $eps-border-color};
6
+ }
7
+
8
+ .eps-theme-dark {
9
+ --eps-sort-select-select-background-color: #{$eps-dark-gray-600};
10
+ --eps-sort-select-select-color: #{$eps-dark-gray-200};
11
+ --eps-sort-select-button-background-color: #{$eps-dark-gray-600};
12
+ --eps-sort-select-button-border: #{1px solid $eps-dark-gray-800};
13
+ }
14
+
15
+ .eps-sort-select {
16
+ width: 100%;
17
+ font-size: type(size, "15");
18
+ display: flex;
19
+
20
+ &__select-wrapper {
21
+ flex: 1;
22
+ position: relative;
23
+
24
+ &::after {
25
+ content: "\e8ad";
26
+ font-family: eicons;
27
+ position: absolute;
28
+ @include end( spacing( 10 ) );
29
+ top: 0;
30
+ bottom: 0;
31
+ color: tints(500);
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ pointer-events: none;
36
+ }
37
+ }
38
+
39
+ &__select {
40
+ width: 100%;
41
+ padding: spacing(10) spacing(10);
42
+ border: none;
43
+ background: var(--eps-sort-select-select-background-color);
44
+ outline: none;
45
+ color: var(--eps-sort-select-select-color);
46
+ line-height: type( line-height, 'flat' );
47
+ cursor: pointer;
48
+ height: spacing(44);
49
+ appearance: none;
50
+ -webkit-appearance:none;
51
+ border-radius: 0;
52
+ }
53
+
54
+ &__button {
55
+ padding: spacing(12) spacing(12);
56
+ background: var(--eps-sort-select-button-background-color);
57
+ @include border-start( var(--eps-sort-select-button-border) );
58
+ line-height: type( line-height, 'flat' );
59
+ color: tints(500);
60
+ }
61
+ }
app/modules/kit-library/assets/js/components/tags-filter.scss ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $root: e-kit-library__tags-filter;
2
+
3
+ .#{$root} {
4
+ --e-kit-library-tags-filter-list-search-background-color: #{theme-colors('light')};
5
+ }
6
+
7
+ .eps-theme-dark {
8
+ .#{$root} {
9
+ --e-kit-library-tags-filter-list-search-background-color: #{dark-tints( 600 )};
10
+ }
11
+ }
12
+
13
+ .e-kit-library__tags-filter {
14
+ margin-top: spacing(44);
15
+
16
+ &-list {
17
+ margin-bottom: spacing(44);
18
+
19
+ .eps-collapse__title {
20
+ padding-right: spacing(30);
21
+ padding-left: spacing(30);
22
+ text-transform: uppercase;
23
+ }
24
+
25
+ .eps-collapse__content {
26
+ margin: spacing(5) spacing(30);
27
+ }
28
+ }
29
+
30
+ &-list-container {
31
+ max-height: 250px;
32
+ overflow: auto;
33
+ }
34
+
35
+ &-list-search {
36
+ margin-bottom: spacing(10);
37
+
38
+ .eps-search-input {
39
+ background: var(--e-kit-library-tags-filter-list-search-background-color);
40
+ }
41
+ }
42
+
43
+ &-list-item {
44
+ padding: spacing(10) 0;
45
+ display: flex;
46
+ align-items: center;
47
+ font-weight: 500;
48
+
49
+ input {
50
+ @include margin-end(spacing(5))
51
+ }
52
+ }
53
+ }
app/modules/kit-library/assets/js/components/taxonomies-filter-list.js ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Taxonomy from '../models/taxonomy';
2
+ import Collapse from './collapse';
3
+ import SearchInput from './search-input';
4
+ import { Checkbox, Text } from '@elementor/app-ui';
5
+ import { sprintf } from '@wordpress/i18n';
6
+ import { useState, useMemo } from 'react';
7
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
8
+
9
+ const MIN_TAGS_LENGTH_FOR_SEARCH_INPUT = 15;
10
+
11
+ const TaxonomiesFilterList = ( props ) => {
12
+ const [ isOpen, setIsOpen ] = useState( props.taxonomiesByType.isOpenByDefault );
13
+ const [ search, setSearch ] = useState( '' );
14
+
15
+ const taxonomies = useMemo( () => {
16
+ if ( ! search ) {
17
+ return props.taxonomiesByType.data;
18
+ }
19
+
20
+ const lowerCaseSearch = search.toLowerCase();
21
+
22
+ return props.taxonomiesByType.data.filter(
23
+ ( tag ) => tag.text.toLowerCase().includes( lowerCaseSearch ),
24
+ );
25
+ }, [ props.taxonomiesByType.data, search ] );
26
+
27
+ const eventTracking = ( command, section, action, item ) => {
28
+ const category = props.category && ( '/favorites' === props.category ? 'favorites' : 'all kits' );
29
+ appsEventTrackingDispatch(
30
+ command,
31
+ {
32
+ page_source: 'home page',
33
+ element_location: 'app_sidebar',
34
+ category,
35
+ section,
36
+ item,
37
+ action: action ? 'checked' : 'unchecked',
38
+ },
39
+ );
40
+ };
41
+
42
+ return (
43
+ <Collapse
44
+ className="e-kit-library__tags-filter-list"
45
+ title={ props.taxonomiesByType.label }
46
+ isOpen={ isOpen }
47
+ onChange={ setIsOpen }
48
+ onClick={ ( collapseState, title ) => {
49
+ props.onCollapseChange?.( collapseState, title );
50
+ } }
51
+ >
52
+ {
53
+ props.taxonomiesByType.data.length >= MIN_TAGS_LENGTH_FOR_SEARCH_INPUT &&
54
+ <SearchInput
55
+ size="sm"
56
+ className="e-kit-library__tags-filter-list-search"
57
+ // Translators: %s is the taxonomy type.
58
+ placeholder={ sprintf( __( 'Search %s...', 'elementor' ), props.taxonomiesByType.label ) }
59
+ value={ search }
60
+ onChange={ ( searchTerm ) => {
61
+ setSearch( searchTerm );
62
+ if ( searchTerm ) {
63
+ props.onChange?.( searchTerm );
64
+ }
65
+ } }
66
+ />
67
+ }
68
+ <div className="e-kit-library__tags-filter-list-container">
69
+ { 0 === taxonomies.length && <Text>{ __( 'No Results Found', 'elementor' ) }</Text> }
70
+ {
71
+ taxonomies.map( ( taxonomy ) => (
72
+ // eslint-disable-next-line jsx-a11y/label-has-for
73
+ <label key={ taxonomy.text } className="e-kit-library__tags-filter-list-item">
74
+ <Checkbox
75
+ checked={ props.selected[ taxonomy.type ]?.includes( taxonomy.text ) || false }
76
+ onChange={ ( e ) => {
77
+ const checked = e.target.checked;
78
+ eventTracking( 'kit-library/filter', taxonomy.type, checked, taxonomy.text );
79
+ props.onSelect( taxonomy.type, ( prev ) => {
80
+ return checked
81
+ ? [ ...prev, taxonomy.text ]
82
+ : prev.filter( ( tagId ) => tagId !== taxonomy.text );
83
+ } );
84
+ } } />
85
+ { taxonomy.text }
86
+ </label>
87
+ ) )
88
+ }
89
+ </div>
90
+ </Collapse>
91
+ );
92
+ };
93
+
94
+ TaxonomiesFilterList.propTypes = {
95
+ taxonomiesByType: PropTypes.shape( {
96
+ key: PropTypes.string,
97
+ label: PropTypes.string,
98
+ data: PropTypes.arrayOf( PropTypes.instanceOf( Taxonomy ) ),
99
+ isOpenByDefault: PropTypes.bool,
100
+ } ),
101
+ selected: PropTypes.objectOf( PropTypes.arrayOf( PropTypes.string ) ),
102
+ onSelect: PropTypes.func,
103
+ onCollapseChange: PropTypes.func,
104
+ category: PropTypes.string,
105
+ onChange: PropTypes.func,
106
+ };
107
+
108
+ export default React.memo( TaxonomiesFilterList );
app/modules/kit-library/assets/js/components/taxonomies-filter.js ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import TaxonomiesFilterList from './taxonomies-filter-list';
2
+ import Taxonomy, { taxonomyType } from '../models/taxonomy';
3
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
4
+
5
+ import './tags-filter.scss';
6
+
7
+ const { useMemo } = React;
8
+
9
+ export default function TaxonomiesFilter( props ) {
10
+ const taxonomiesByType = useMemo( () => {
11
+ if ( ! props.taxonomies ) {
12
+ return [];
13
+ }
14
+
15
+ return taxonomyType
16
+ .map( ( tagType ) => ( {
17
+ ...tagType,
18
+ data: props.taxonomies.filter( ( item ) => item.type === tagType.key ),
19
+ } ) )
20
+ .filter( ( { data } ) => data.length > 0 );
21
+ }, [ props.taxonomies ] ),
22
+ eventTracking = ( command, search, section, eventType = 'click' ) => appsEventTrackingDispatch(
23
+ command,
24
+ {
25
+ page_source: 'home page',
26
+ element_location: 'app_sidebar',
27
+ category: props.category && ( '/favorites' === props.category ? 'favorites' : 'all kits' ),
28
+ section,
29
+ search_term: search,
30
+ event_type: eventType,
31
+ },
32
+ );
33
+
34
+ return (
35
+ <div className="e-kit-library__tags-filter">
36
+ {
37
+ taxonomiesByType.map( ( group ) => (
38
+ <TaxonomiesFilterList
39
+ key={ group.key }
40
+ taxonomiesByType={ group }
41
+ selected={ props.selected }
42
+ onSelect={ props.onSelect }
43
+ onCollapseChange={ ( collapseState, title ) => {
44
+ const command = collapseState ? 'kit-library/collapse' : 'kit-library/expand';
45
+ eventTracking( command, null, title );
46
+ } }
47
+ onChange={ ( search ) => {
48
+ eventTracking( 'kit-library/filter', search, group.label, 'search' );
49
+ } }
50
+ category={ props.category }
51
+ />
52
+ ) )
53
+ }
54
+ </div>
55
+ );
56
+ }
57
+
58
+ TaxonomiesFilter.propTypes = {
59
+ selected: PropTypes.objectOf( PropTypes.arrayOf( PropTypes.string ) ),
60
+ onSelect: PropTypes.func,
61
+ taxonomies: PropTypes.arrayOf( PropTypes.instanceOf( Taxonomy ) ),
62
+ category: PropTypes.string,
63
+ };
app/modules/kit-library/assets/js/context/last-filter-context.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createContext, useContext, useState } from 'react';
2
+
3
+ const LastFilterContext = createContext( {} );
4
+
5
+ /**
6
+ * Consume the context
7
+ *
8
+ * @return {{}} context value
9
+ */
10
+ export function useLastFilterContext() {
11
+ return useContext( LastFilterContext );
12
+ }
13
+
14
+ /**
15
+ * Settings Provider
16
+ *
17
+ * @param {*} props
18
+ * @return {JSX.Element} element
19
+ * @function Object() { [native code] }
20
+ */
21
+ export function LastFilterProvider( props ) {
22
+ const [ lastFilter, setLastFilter ] = useState( {} );
23
+
24
+ return (
25
+ <LastFilterContext.Provider value={ { lastFilter, setLastFilter } }>
26
+ { props.children }
27
+ </LastFilterContext.Provider>
28
+ );
29
+ }
30
+
31
+ LastFilterProvider.propTypes = {
32
+ children: PropTypes.any,
33
+ };
app/modules/kit-library/assets/js/context/settings-context.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createContext, useContext, useState, useEffect, useCallback } from 'react';
2
+
3
+ const SettingsContext = createContext( {} );
4
+
5
+ /**
6
+ * Consume the context
7
+ *
8
+ * @return {{emptyTrashDays: number}} context value
9
+ */
10
+ export function useSettingsContext() {
11
+ return useContext( SettingsContext );
12
+ }
13
+
14
+ /**
15
+ * Settings Provider
16
+ *
17
+ * @param {*} props
18
+ * @return {JSX.Element} element
19
+ * @function Object() { [native code] }
20
+ */
21
+ export function SettingsProvider( props ) {
22
+ const [ settings, setSettings ] = useState( {} );
23
+
24
+ const updateSettings = useCallback( ( newSettings ) => {
25
+ setSettings( ( prev ) => ( { ...prev, ...newSettings } ) );
26
+ }, [ setSettings ] );
27
+
28
+ useEffect( () => {
29
+ setSettings( props.value );
30
+ }, [ setSettings ] );
31
+
32
+ return (
33
+ <SettingsContext.Provider value={ { settings, setSettings, updateSettings } }>
34
+ { props.children }
35
+ </SettingsContext.Provider>
36
+ );
37
+ }
38
+
39
+ SettingsProvider.propTypes = {
40
+ children: PropTypes.any,
41
+ value: PropTypes.object.isRequired,
42
+ };
app/modules/kit-library/assets/js/data/kits/commands-data/download-link.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ export class DownloadLink extends $e.modules.CommandData {
2
+ static getEndpointFormat() {
3
+ return 'kits/download-link/{id}';
4
+ }
5
+ }
app/modules/kit-library/assets/js/data/kits/commands-data/favorites.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ export class Favorites extends $e.modules.CommandData {
2
+ static getEndpointFormat() {
3
+ return 'kits/favorites/{id}';
4
+ }
5
+ }
app/modules/kit-library/assets/js/data/kits/commands-data/index.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ export { DownloadLink } from './download-link';
2
+ export { Favorites } from './favorites';
3
+
4
+ export class Index extends $e.modules.CommandData {
5
+ static getEndpointFormat() {
6
+ return 'kits/{id}';
7
+ }
8
+ }
app/modules/kit-library/assets/js/data/kits/component.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as commandsData from './commands-data/';
2
+
3
+ export default class Component extends $e.modules.ComponentBase {
4
+ getNamespace() {
5
+ return 'kits';
6
+ }
7
+
8
+ defaultData() {
9
+ return this.importCommands( commandsData );
10
+ }
11
+ }
app/modules/kit-library/assets/js/data/taxonomies/commands-data/index.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ export class Index extends $e.modules.CommandData {
2
+ static getEndpointFormat() {
3
+ return 'kit-taxonomies/{id}';
4
+ }
5
+ }
app/modules/kit-library/assets/js/data/taxonomies/component.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as commandsData from './commands-data/';
2
+
3
+ export default class Component extends $e.modules.ComponentBase {
4
+ getNamespace() {
5
+ return 'kit-taxonomies';
6
+ }
7
+
8
+ defaultData() {
9
+ return this.importCommands( commandsData );
10
+ }
11
+ }
app/modules/kit-library/assets/js/e-component.js ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default class EComponent extends $e.modules.ComponentBase {
2
+ /**
3
+ * @return {string} the namespace of the component
4
+ */
5
+ getNamespace() {
6
+ return 'kit-library';
7
+ }
8
+
9
+ /**
10
+ * @return {*} All the commands of the components
11
+ */
12
+ defaultCommands() {
13
+ const trackingCommands = [
14
+ 'apply-kit',
15
+ 'approve-import',
16
+ 'approve-selection',
17
+ 'back-to-library',
18
+ 'browse',
19
+ 'change-sort-direction',
20
+ 'change-sort-type',
21
+ 'change-sort-value',
22
+ 'check',
23
+ 'check-item',
24
+ 'check-out-kit',
25
+ 'checking-a-checkbox',
26
+ 'check-kits-on-theme-forest',
27
+ 'checkbox-filtration',
28
+ 'collapse',
29
+ 'choose-file',
30
+ 'choose-site-parts-to-import',
31
+ 'clear-filter',
32
+ 'close',
33
+ 'drop',
34
+ 'enable',
35
+ 'expand',
36
+ 'file-upload',
37
+ 'filter',
38
+ 'filter-selection',
39
+ 'favorite-icon',
40
+ 'go-back',
41
+ 'go-back-to-view-kits',
42
+ 'kit-free-search',
43
+ 'kit-is-live-load',
44
+ 'kit-import',
45
+ 'logo',
46
+ 'mark-as-favorite',
47
+ 'modal-close',
48
+ 'modal-load',
49
+ 'modal-open',
50
+ 'modal-error',
51
+ 'open-site-area',
52
+ 'refetch',
53
+ 'responsive-controls',
54
+ 'see-it-live',
55
+ 'seek-more-info',
56
+ 'sidebar-tag-filter',
57
+ 'skip',
58
+ 'select-organizing-category',
59
+ 'top-bar-change-view',
60
+ 'uncheck',
61
+ 'unchecking-a-checkbox',
62
+ 'view-demo-page',
63
+ 'view-demo-part',
64
+ 'view-overview-page',
65
+ ].reduce( ( allCommands, command ) => ( {
66
+ ...allCommands,
67
+ [ command ]: () => {
68
+ },
69
+ } ), {} );
70
+
71
+ return {
72
+ ...trackingCommands,
73
+ };
74
+ }
75
+ }
app/modules/kit-library/assets/js/hooks/use-content-types.js ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ContentType from '../models/content-type';
2
+ import { useQuery } from 'react-query';
3
+
4
+ export const KEY = 'content-types';
5
+
6
+ /**
7
+ * The data should come from the server, this is a temp solution that helps to demonstrate that data comes from the server
8
+ * but for now this is a local data.
9
+ *
10
+ * @return {import('react-query').UseQueryResult<Promise.constructor, unknown>} result
11
+ */
12
+ export default function useContentTypes() {
13
+ return useQuery( [ KEY ], fetchContentTypes );
14
+ }
15
+
16
+ /**
17
+ * @return {Promise.constructor} content types
18
+ */
19
+ function fetchContentTypes() {
20
+ return Promise.resolve( [
21
+ {
22
+ id: 'page',
23
+ label: __( 'Pages', 'elementor' ),
24
+ doc_types: [ 'wp-page' ],
25
+ order: 0,
26
+ },
27
+ {
28
+ id: 'site-parts',
29
+ label: __( 'Site Parts', 'elementor' ),
30
+ doc_types: [
31
+ 'archive',
32
+ 'error-404',
33
+ 'footer',
34
+ 'header',
35
+ 'search-results',
36
+ 'single-page',
37
+ 'single-post',
38
+
39
+ // WooCommerce types
40
+ 'product',
41
+ 'product-archive',
42
+
43
+ // Legacy Types
44
+ '404',
45
+ 'single',
46
+ ],
47
+ order: 1,
48
+ },
49
+ {
50
+ id: 'popup',
51
+ label: __( 'Popups', 'elementor' ),
52
+ doc_types: [ 'popup' ],
53
+ order: 2,
54
+ },
55
+ ] ).then( ( data ) => {
56
+ return data.map( ( contentType ) => ContentType.createFromResponse( contentType ) );
57
+ } );
58
+ }
app/modules/kit-library/assets/js/hooks/use-debounced-callback.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useRef, useCallback } from 'react';
2
+
3
+ export default function useDebouncedCallback( callback, wait ) {
4
+ const timeout = useRef();
5
+
6
+ return useCallback(
7
+ ( ...args ) => {
8
+ const later = () => {
9
+ clearTimeout( timeout.current );
10
+
11
+ callback( ...args );
12
+ };
13
+
14
+ clearTimeout( timeout.current );
15
+
16
+ timeout.current = setTimeout( later, wait );
17
+ },
18
+ [ callback, wait ],
19
+ );
20
+ }
app/modules/kit-library/assets/js/hooks/use-download-link-mutation.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useCallback } from 'react';
2
+ import { useMutation } from 'react-query';
3
+
4
+ export default function useDownloadLinkMutation( model, { onError, onSuccess } ) {
5
+ const downloadLink = useCallback(
6
+ () => $e.data.get( 'kits/download-link', { id: model.id }, { refresh: true } ),
7
+ [ model ],
8
+ );
9
+
10
+ return useMutation(
11
+ downloadLink,
12
+ { onSuccess, onError },
13
+ );
14
+ }
app/modules/kit-library/assets/js/hooks/use-kit-call-to-action.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useMemo } from 'react';
2
+ import { useSettingsContext } from '../context/settings-context';
3
+
4
+ export const TYPE_CONNECT = 'connect';
5
+ export const TYPE_PROMOTION = 'promotion';
6
+ export const TYPE_APPLY = 'apply';
7
+
8
+ export default function useKitCallToAction( kitAccessLevel ) {
9
+ const { settings } = useSettingsContext();
10
+
11
+ // SubscriptionPlan can be null when the context is not filled (can be happened when using back button in the browser.)
12
+ const subscriptionPlan = useMemo( () => settings.subscription_plans?.[ kitAccessLevel ], [ settings, kitAccessLevel ] );
13
+
14
+ const type = useMemo( () => {
15
+ // The user can apply this kit (the user access level is equal or greater then the kit access level).
16
+ const isAuthorizeToApplyKit = settings.access_level >= kitAccessLevel;
17
+
18
+ // The user in not connected and has pro plugin or the kit is a free kit.
19
+ if ( ! settings.is_library_connected && ( settings.is_pro || isAuthorizeToApplyKit ) ) {
20
+ return TYPE_CONNECT;
21
+ }
22
+
23
+ // The user is connected or has only core plugin and cannot access this kit.
24
+ if ( ! isAuthorizeToApplyKit ) {
25
+ return TYPE_PROMOTION;
26
+ }
27
+
28
+ // The user is connected and can access the kit.
29
+ return TYPE_APPLY;
30
+ }, [ settings, kitAccessLevel ] );
31
+
32
+ return [ type, { subscriptionPlan } ];
33
+ }
app/modules/kit-library/assets/js/hooks/use-kit-document-by-type.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import useContentTypes from './use-content-types';
2
+ import { useMemo } from 'react';
3
+
4
+ export default function useKitDocumentByType( kit ) {
5
+ const contentTypesQuery = useContentTypes();
6
+
7
+ const data = useMemo( () => {
8
+ if ( ! kit || ! contentTypesQuery.data ) {
9
+ return [];
10
+ }
11
+
12
+ return kit.getDocumentsByTypes( contentTypesQuery.data )
13
+ .sort( ( a, b ) => a.order - b.order );
14
+ }, [ kit, contentTypesQuery.data ] );
15
+
16
+ return {
17
+ ...contentTypesQuery,
18
+ data,
19
+ };
20
+ }
app/modules/kit-library/assets/js/hooks/use-kit-favorites-mutations.js ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useCallback } from 'react';
2
+ import { KEY as kitsKey } from '../hooks/use-kits';
3
+ import { KEY as kitKey } from '../hooks/use-kit';
4
+ import { useMutation, useQueryClient } from 'react-query';
5
+
6
+ export function useKitFavoritesMutations() {
7
+ const queryClient = useQueryClient();
8
+
9
+ const onSuccess = useCallback( ( { data } ) => {
10
+ const id = data.data.id;
11
+ const isFavorite = data.data.is_favorite;
12
+
13
+ // Update the kit list if the list exists.
14
+ if ( queryClient.getQueryData( [ kitsKey ] ) ) {
15
+ queryClient.setQueryData(
16
+ [ kitsKey ],
17
+ ( kits ) => {
18
+ if ( ! kits ) {
19
+ return kits;
20
+ }
21
+
22
+ return kits.map( ( item ) => {
23
+ if ( item.id === id ) {
24
+ item.isFavorite = isFavorite;
25
+
26
+ // Should return a new kit to trigger rerender.
27
+ return item.clone();
28
+ }
29
+
30
+ return item;
31
+ } );
32
+ },
33
+ );
34
+ }
35
+
36
+ // Update specific kit if the kit exists
37
+ if ( queryClient.getQueryData( [ kitKey, id ] ) ) {
38
+ queryClient.setQueryData(
39
+ [ kitKey, id ],
40
+ ( currentKit ) => {
41
+ currentKit.isFavorite = isFavorite;
42
+
43
+ // Should return a new kit to trigger rerender.
44
+ return currentKit.clone();
45
+ },
46
+ );
47
+ }
48
+ }, [ queryClient ] );
49
+
50
+ const addToFavorites = useMutation(
51
+ ( id ) => $e.data.create( 'kits/favorites', {}, { id } ),
52
+ { onSuccess },
53
+ );
54
+ const removeFromFavorites = useMutation(
55
+ ( id ) => $e.data.delete( 'kits/favorites', { id } ),
56
+ { onSuccess },
57
+ );
58
+
59
+ return {
60
+ addToFavorites,
61
+ removeFromFavorites,
62
+ isLoading: addToFavorites.isLoading || removeFromFavorites.isLoading,
63
+ };
64
+ }
app/modules/kit-library/assets/js/hooks/use-kit.js ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Kit from '../models/kit';
2
+ import { KEY as LIST_KEY } from './use-kits';
3
+ import { useCallback } from 'react';
4
+ import { useQuery, useQueryClient } from 'react-query';
5
+
6
+ export const KEY = 'kit';
7
+
8
+ export default function useKit( id ) {
9
+ // A function that returns existing data from the kit list for a placeholder data before the kit request will resolved.
10
+ const placeholderDataCallback = usePlaceholderDataCallback( id );
11
+
12
+ return useQuery( [ KEY, id ], fetchKitItem, {
13
+ placeholderData: placeholderDataCallback,
14
+ },
15
+ );
16
+ }
17
+
18
+ /**
19
+ * Return placeholder function for kit query.
20
+ *
21
+ * @param {*} id
22
+ * @return {function(): (undefined|*)} placeholder
23
+ */
24
+ function usePlaceholderDataCallback( id ) {
25
+ const queryClient = useQueryClient();
26
+
27
+ return useCallback( () => {
28
+ const placeholder = queryClient.getQueryData( LIST_KEY )
29
+ ?.find( ( kit ) => {
30
+ return kit.id === id;
31
+ } );
32
+
33
+ if ( ! placeholder ) {
34
+ return;
35
+ }
36
+
37
+ return placeholder;
38
+ }, [ queryClient, id ] );
39
+ }
40
+
41
+ /**
42
+ * Fetch kit
43
+ *
44
+ * @param {Object} root0
45
+ * @param {Object} root0.queryKey
46
+ * @param {*} root0.queryKey.0
47
+ * @param {string} root0.queryKey.1
48
+ * @return {Promise<Kit>} kit
49
+ */
50
+ // eslint-disable-next-line no-unused-vars
51
+ function fetchKitItem( { queryKey: [ _, id ] } ) {
52
+ return $e.data.get( 'kits/index', { id }, { refresh: true } )
53
+ .then( ( response ) => response.data )
54
+ .then( ( { data } ) => Kit.createFromResponse( data ) );
55
+ }
app/modules/kit-library/assets/js/hooks/use-kits.js ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Kit from '../models/kit';
2
+ import useSelectedTaxonomies from './use-selected-taxonomies';
3
+ import { taxonomyType } from '../models/taxonomy';
4
+ import { useQuery } from 'react-query';
5
+ import { useState, useMemo, useCallback, useEffect } from 'react';
6
+
7
+ export const KEY = 'kits';
8
+
9
+ /**
10
+ * The default query params
11
+ *
12
+ * @type {Object}
13
+ */
14
+ export const defaultQueryParams = {
15
+ favorite: false,
16
+ search: '',
17
+ taxonomies: taxonomyType.reduce( ( current, { key } ) => {
18
+ return {
19
+ ...current,
20
+ [ key ]: [],
21
+ };
22
+ }, {} ),
23
+ order: {
24
+ direction: 'asc',
25
+ by: 'featuredIndex',
26
+ },
27
+ referrer: null,
28
+ };
29
+
30
+ const kitsPipeFunctions = {
31
+ /**
32
+ * Filter by favorite
33
+ *
34
+ * @param {Array<*>} data
35
+ * @param {*} queryParams
36
+ * @return {Array} filtered data
37
+ */
38
+ favoriteFilter: ( data, queryParams ) => {
39
+ if ( ! queryParams.favorite ) {
40
+ return data;
41
+ }
42
+
43
+ return data.filter( ( item ) => item.isFavorite );
44
+ },
45
+
46
+ /**
47
+ * Filter by search term.
48
+ *
49
+ * @param {Array<*>} data
50
+ * @param {*} queryParams
51
+ * @return {Array} filtered data
52
+ */
53
+ searchFilter: ( data, queryParams ) => {
54
+ if ( ! queryParams.search ) {
55
+ return data;
56
+ }
57
+
58
+ return data.filter( ( item ) => {
59
+ const keywords = [ ...item.keywords, ...item.taxonomies, item.title ];
60
+ const searchTerm = queryParams.search.toLowerCase();
61
+
62
+ return keywords.some( ( keyword ) => keyword.toLowerCase().includes( searchTerm ) );
63
+ } );
64
+ },
65
+
66
+ /**
67
+ * Filter by taxonomies.
68
+ * In each taxonomy type it use the OR operator and between types it uses the AND operator.
69
+ *
70
+ * @param {Array<*>} data
71
+ * @param {*} queryParams
72
+ * @return {Array} filtered data
73
+ */
74
+ taxonomiesFilter: ( data, queryParams ) => {
75
+ return Object.values( queryParams.taxonomies )
76
+ .filter( ( taxonomies ) => taxonomies.length )
77
+ .reduce( ( current, taxonomies ) => current.filter( ( item ) =>
78
+ taxonomies.some( ( taxonomy ) =>
79
+ item.taxonomies.some( ( itemTaxonomy ) => taxonomy === itemTaxonomy ),
80
+ ) ),
81
+ data,
82
+ );
83
+ },
84
+
85
+ /**
86
+ * Sort all the data by the "order" query param
87
+ *
88
+ * @param {Array<*>} data
89
+ * @param {*} queryParams
90
+ * @return {Array} sorted data
91
+ */
92
+ sort: ( data, queryParams ) => {
93
+ const order = queryParams.order;
94
+
95
+ return data.sort( ( item1, item2 ) => {
96
+ if ( 'asc' === order.direction ) {
97
+ return item1[ order.by ] - item2[ order.by ];
98
+ }
99
+
100
+ return item2[ order.by ] - item1[ order.by ];
101
+ } );
102
+ },
103
+ };
104
+
105
+ /**
106
+ * A util function to transform data throw transform functions
107
+ *
108
+ * @param {Array<Function>} functions
109
+ * @return {function(*=, ...[*]): *} function
110
+ */
111
+ function pipe( ...functions ) {
112
+ return ( value, ...args ) =>
113
+ functions.reduce(
114
+ ( currentValue, currentFunction ) => currentFunction( currentValue, ...args ),
115
+ value,
116
+ );
117
+ }
118
+
119
+ /**
120
+ * Fetch kits
121
+ *
122
+ * @param {boolean} force
123
+ * @return {*} kits
124
+ */
125
+ function fetchKits( force ) {
126
+ return $e.data.get( 'kits/index', {
127
+ force: force ? 1 : undefined,
128
+ }, { refresh: true } )
129
+ .then( ( response ) => response.data )
130
+ .then( ( { data } ) => data.map( ( item ) => Kit.createFromResponse( item ) ) );
131
+ }
132
+
133
+ /**
134
+ * Main function.
135
+ *
136
+ * @param {*} initialQueryParams
137
+ * @return {Object} query
138
+ */
139
+ export default function useKits( initialQueryParams = {} ) {
140
+ const [ force, setForce ] = useState( false );
141
+ const [ queryParams, setQueryParams ] = useState( () => ( {
142
+ ready: false, // When the query param is ready to use (after parsing and assign the query param from the url)
143
+ ...defaultQueryParams,
144
+ ...initialQueryParams,
145
+ } ) );
146
+
147
+ const forceRefetch = useCallback( () => setForce( true ), [ setForce ] );
148
+
149
+ const clearQueryParams = useCallback(
150
+ () => setQueryParams( { ready: true, ...defaultQueryParams, ...initialQueryParams } ),
151
+ [ setQueryParams ],
152
+ );
153
+
154
+ const query = useQuery( [ KEY ], () => fetchKits( force ) );
155
+
156
+ const data = useMemo(
157
+ () => ! query.data
158
+ ? []
159
+ : pipe( ...Object.values( kitsPipeFunctions ) )( [ ...query.data ], queryParams ),
160
+ [ query.data, queryParams ],
161
+ );
162
+
163
+ const selectedTaxonomies = useSelectedTaxonomies( queryParams.taxonomies );
164
+
165
+ const isFilterActive = useMemo(
166
+ () => !! queryParams.search || !! selectedTaxonomies.length,
167
+ [ queryParams ],
168
+ );
169
+
170
+ useEffect( () => {
171
+ if ( ! force ) {
172
+ return;
173
+ }
174
+
175
+ query.refetch().then( () => setForce( false ) );
176
+ }, [ force ] );
177
+
178
+ return {
179
+ ...query,
180
+ data,
181
+ queryParams,
182
+ setQueryParams,
183
+ clearQueryParams,
184
+ forceRefetch,
185
+ isFilterActive,
186
+ };
187
+ }
app/modules/kit-library/assets/js/hooks/use-selected-taxonomies.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ import { useMemo } from 'react';
2
+
3
+ export default function useSelectedTaxonomies( taxonomiesFilter ) {
4
+ return useMemo(
5
+ () => Object.values( taxonomiesFilter )
6
+ .reduce( ( current, groupedTaxonomies ) => [ ...current, ...groupedTaxonomies ] ),
7
+ [ taxonomiesFilter ],
8
+ );
9
+ }
app/modules/kit-library/assets/js/hooks/use-taxonomies.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Taxonomy from '../models/taxonomy';
2
+ import { useQuery } from 'react-query';
3
+ import { useState, useCallback, useEffect } from 'react';
4
+
5
+ export const KEY = 'tags';
6
+
7
+ export default function useTaxonomies() {
8
+ const [ force, setForce ] = useState( false );
9
+
10
+ const forceRefetch = useCallback( () => setForce( true ), [ setForce ] );
11
+
12
+ const query = useQuery( [ KEY ], () => fetchTaxonomies( force ) );
13
+
14
+ useEffect( () => {
15
+ if ( ! force ) {
16
+ return;
17
+ }
18
+
19
+ query.refetch().then( () => setForce( false ) );
20
+ }, [ force ] );
21
+
22
+ return {
23
+ ...query,
24
+ forceRefetch,
25
+ };
26
+ }
27
+
28
+ function fetchTaxonomies( force ) {
29
+ return $e.data.get( 'kit-taxonomies/index', {
30
+ force: force ? 1 : undefined,
31
+ }, { refresh: true } )
32
+ .then( ( response ) => response.data )
33
+ .then( ( { data } ) => data.map( ( taxonomy ) => Taxonomy.createFromResponse( taxonomy ) ) );
34
+ }
app/modules/kit-library/assets/js/kit-library.js ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ import KitLibraryComponent from './e-component';
2
+
3
+ window.top.$e.components.register( new KitLibraryComponent() );
app/modules/kit-library/assets/js/models/base-model.js ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default class BaseModel {
2
+ /**
3
+ * Clone to object to avoid changing the reference.
4
+ *
5
+ * @return {BaseModel} cloned model
6
+ */
7
+ clone() {
8
+ const instance = new this.constructor();
9
+
10
+ Object.keys( this ).forEach( ( key ) => {
11
+ instance[ key ] = this[ key ];
12
+ } );
13
+
14
+ return instance;
15
+ }
16
+
17
+ /**
18
+ * Using init and not the default constructor because there is a problem to fill the instance
19
+ * dynamically in the constructor.
20
+ *
21
+ * @param {*} data
22
+ * @return {BaseModel} model
23
+ */
24
+ init( data = {} ) {
25
+ Object.entries( data ).forEach( ( [ key, value ] ) => {
26
+ this[ key ] = value;
27
+ } );
28
+
29
+ return this;
30
+ }
31
+ }
app/modules/kit-library/assets/js/models/content-type.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import BaseModel from './base-model';
2
+
3
+ export default class ContentType extends BaseModel {
4
+ id = '';
5
+ label = '';
6
+ documentTypes = [];
7
+ documents = [];
8
+ order = 0;
9
+
10
+ static createFromResponse( documentType ) {
11
+ return new ContentType().init( {
12
+ id: documentType.id,
13
+ label: documentType.label,
14
+ documentTypes: documentType.doc_types,
15
+ order: documentType.order,
16
+ documents: [],
17
+ } );
18
+ }
19
+ }
app/modules/kit-library/assets/js/models/document.js ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import BaseModel from './base-model';
2
+
3
+ export default class Document extends BaseModel {
4
+ id = '';
5
+ title = '';
6
+ documentType = '';
7
+ thumbnailUrl = '';
8
+ previewUrl = '';
9
+
10
+ /**
11
+ * Create a tag from server response
12
+ *
13
+ * @param {Document} document
14
+ */
15
+ static createFromResponse( document ) {
16
+ return new Document().init( {
17
+ id: document.id,
18
+ title: document.title,
19
+ documentType: document.doc_type,
20
+ thumbnailUrl: document.thumbnail_url,
21
+ previewUrl: document.preview_url,
22
+ } );
23
+ }
24
+ }
app/modules/kit-library/assets/js/models/kit.js ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import BaseModel from './base-model';
2
+ import Document from './document';
3
+
4
+ /**
5
+ * @typedef {import('./content-type')} ContentType
6
+ */
7
+ export default class Kit extends BaseModel {
8
+ id = '';
9
+ title = '';
10
+ description = '';
11
+ isFavorite = false;
12
+ thumbnailUrl = null;
13
+ previewUrl = '';
14
+ accessLevel = 0;
15
+ trendIndex = null;
16
+ popularityIndex = null;
17
+ featuredIndex = null;
18
+ createdAt = null;
19
+ updatedAt = null;
20
+ keywords = [];
21
+ taxonomies = [];
22
+ documents = [];
23
+
24
+ /**
25
+ * Create a kit from server response
26
+ *
27
+ * @param {Kit} kit
28
+ */
29
+ static createFromResponse( kit ) {
30
+ return new Kit().init( {
31
+ id: kit.id,
32
+ title: kit.title,
33
+ description: kit.description,
34
+ isFavorite: kit.is_favorite,
35
+ thumbnailUrl: kit.thumbnail_url,
36
+ previewUrl: kit.preview_url,
37
+ accessLevel: kit.access_level,
38
+ trendIndex: kit.trend_index,
39
+ popularityIndex: kit.popularity_index,
40
+ featuredIndex: kit.featured_index,
41
+ // TODO: Remove when the API is stable (when date params always exists)
42
+ createdAt: kit.created_at ? new Date( kit.created_at ) : null,
43
+ updatedAt: kit.updated_at ? new Date( kit.updated_at ) : null,
44
+ //
45
+ keywords: kit.keywords,
46
+ taxonomies: kit.taxonomies,
47
+ documents: kit.documents
48
+ ? kit.documents.map( ( document ) => Document.createFromResponse( document ) )
49
+ : [],
50
+ } );
51
+ }
52
+
53
+ /**
54
+ * Get content types as param and group all the documents based on it.
55
+ *
56
+ * @param {ContentType[]} contentTypes
57
+ * @return {ContentType[]} content types
58
+ */
59
+ getDocumentsByTypes( contentTypes ) {
60
+ return contentTypes.map( ( contentType ) => {
61
+ contentType = contentType.clone();
62
+
63
+ contentType.documents = this.documents
64
+ .filter( ( document ) => contentType.documentTypes.includes( document.documentType ) );
65
+
66
+ return contentType;
67
+ } );
68
+ }
69
+ }
app/modules/kit-library/assets/js/models/taxonomy.js ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import BaseModel from './base-model';
2
+
3
+ export const taxonomyType = [
4
+ {
5
+ key: 'categories',
6
+ label: __( 'Categories', 'elementor' ),
7
+ isOpenByDefault: true,
8
+ },
9
+ {
10
+ key: 'tags',
11
+ label: __( 'Tags', 'elementor' ),
12
+ },
13
+ {
14
+ key: 'features',
15
+ label: __( 'Features', 'elementor' ),
16
+ },
17
+ {
18
+ key: 'subscription_plans',
19
+ label: __( 'Kits by plan', 'elementor' ),
20
+ },
21
+ ];
22
+
23
+ export default class Taxonomy extends BaseModel {
24
+ text = '';
25
+ type = 'tag';
26
+
27
+ /**
28
+ * Create a tag from server response
29
+ *
30
+ * @param {Taxonomy} taxonomy
31
+ */
32
+ static createFromResponse( taxonomy ) {
33
+ return new Taxonomy().init( {
34
+ text: taxonomy.text,
35
+ type: taxonomy.type,
36
+ } );
37
+ }
38
+ }
app/modules/kit-library/assets/js/module.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import KitsComponent from './data/kits/component';
2
+ import router from '@elementor/router';
3
+ import TaxonomiesComponent from './data/taxonomies/component';
4
+ import KitLibraryComponent from './e-component';
5
+
6
+ export default class KitLibrary {
7
+ constructor() {
8
+ if ( ! this.hasAccessToModule() ) {
9
+ return;
10
+ }
11
+
12
+ $e.components.register( new KitsComponent() );
13
+ $e.components.register( new TaxonomiesComponent() );
14
+ $e.components.register( new KitLibraryComponent() );
15
+
16
+ router.addRoute( {
17
+ path: '/kit-library/*',
18
+ component: React.lazy( () => import( /* webpackChunkName: 'kit-library' */ './app' ) ),
19
+ } );
20
+ }
21
+
22
+ hasAccessToModule() {
23
+ return elementorAppConfig[ 'kit-library' ]?.has_access_to_module;
24
+ }
25
+ }
app/modules/kit-library/assets/js/pages/favorites/favorites.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Index from '../index/index';
2
+ import ErrorScreen from '../../components/error-screen';
3
+ import { useNavigate } from '@reach/router';
4
+
5
+ export default function Favorites( props ) {
6
+ const navigate = useNavigate();
7
+
8
+ const indexNotResultsFavorites = <ErrorScreen
9
+ // eslint-disable-next-line @wordpress/i18n-ellipsis
10
+ title={ __( 'No favorites here yet...', 'elementor' ) }
11
+ description={ __( 'Use the heart icon to save kits that inspire you. You\'ll be able to find them here.', 'elementor' ) }
12
+ button={ {
13
+ text: __( 'Continue browsing.', 'elementor' ),
14
+ action: () => navigate( '/kit-library' ),
15
+ } }
16
+ />;
17
+
18
+ return <Index
19
+ path={ props.path }
20
+ initialQueryParams={ { favorite: true } }
21
+ renderNoResultsComponent={ ( { defaultComponent, isFilterActive } ) => {
22
+ if ( ! isFilterActive ) {
23
+ return indexNotResultsFavorites;
24
+ }
25
+
26
+ return defaultComponent;
27
+ } }
28
+ />;
29
+ }
30
+
31
+ Favorites.propTypes = {
32
+ path: PropTypes.string,
33
+ };
app/modules/kit-library/assets/js/pages/index/index-header.js ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Header from '../../components/layout/header';
2
+ import { ModalProvider, Heading, Text, Button } from '@elementor/app-ui';
3
+ import { useMemo, useState, useRef } from 'react';
4
+ import { useNavigate } from '@reach/router';
5
+ import PopoverDialog from 'elementor-app/ui/popover-dialog/popover-dialog';
6
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
7
+
8
+ import './index-header.scss';
9
+
10
+ export default function IndexHeader( props ) {
11
+ const navigate = useNavigate();
12
+ const [ isInfoModalOpen, setIsInfoModalOpen ] = useState( false );
13
+ const importRef = useRef();
14
+ const eventTracking = ( command, element = null, eventType = 'click', modalType = null ) => {
15
+ appsEventTrackingDispatch(
16
+ command,
17
+ {
18
+ element,
19
+ event_type: eventType,
20
+ page_source: 'home page',
21
+ element_position: 'app_header',
22
+ modal_type: modalType,
23
+ },
24
+ );
25
+ };
26
+ const onClose = ( e ) => {
27
+ const element = e.target.classList.contains( 'eps-modal__overlay' ) ? 'overlay' : 'x';
28
+ eventTracking( 'kit-library/modal-close', element, null, 'info' );
29
+ };
30
+ const buttons = useMemo( () => [
31
+ {
32
+ id: 'info',
33
+ text: __( 'Info', 'elementor-pro' ),
34
+ hideText: true,
35
+ icon: 'eicon-info-circle-o',
36
+ onClick: () => {
37
+ eventTracking( 'kit-library/seek-more-info' );
38
+ setIsInfoModalOpen( true );
39
+ },
40
+ },
41
+ {
42
+ id: 'refetch',
43
+ text: __( 'Refetch', 'elementor-pro' ),
44
+ hideText: true,
45
+ icon: `eicon-sync ${ props.isFetching ? 'eicon-animation-spin' : '' }`,
46
+ onClick: () => {
47
+ eventTracking( 'kit-library/refetch' );
48
+ props.refetch();
49
+ },
50
+ },
51
+ {
52
+ id: 'import',
53
+ text: __( 'Import', 'elementor-pro' ),
54
+ hideText: true,
55
+ icon: 'eicon-upload-circle-o',
56
+ elRef: importRef,
57
+ onClick: () => {
58
+ eventTracking( 'kit-library/kit-import' );
59
+ navigate( '/import?referrer=kit-library' );
60
+ },
61
+ },
62
+ ], [ props.isFetching, props.refetch ] );
63
+
64
+ return (
65
+ <>
66
+ <Header buttons={ buttons } />
67
+ <PopoverDialog
68
+ targetRef={ importRef }
69
+ wrapperClass="e-kit-library__tooltip"
70
+ >
71
+ { __( 'Import Kit', 'elementor' ) }
72
+ </PopoverDialog>
73
+ <ModalProvider title={ __( 'Welcome to the Library', 'elementor' ) }
74
+ show={ isInfoModalOpen }
75
+ setShow={ setIsInfoModalOpen }
76
+ onOpen={ () => eventTracking( 'kit-library/modal-open', null, 'load', 'info' ) }
77
+ onClose={ ( e ) => onClose( e ) }
78
+ >
79
+ <div className="e-kit-library-header-info-modal-container">
80
+ <Heading tag="h3" variant="h3">{ __( 'What\'s a Website Kit?', 'elementor' ) }</Heading>
81
+ <Text>{ __( 'A Website Kit is full, ready-made design that you can apply to your site. It includes all the pages, parts, settings and content that you\'d expect in a fully functional website.', 'elementor' ) }</Text>
82
+ </div>
83
+ <div className="e-kit-library-header-info-modal-container">
84
+ <Heading tag="h3" variant="h3">{ __( 'What\'s going on in the Kit Library?', 'elementor' ) }</Heading>
85
+ <Text>
86
+ { __( 'Search & filter for kits by category and tags, or browse through individual kits to see what\'s inside.', 'elementor' ) }
87
+ <br />
88
+ { __( 'Once you\'ve picked a winner, apply it to your site!', 'elementor' ) }
89
+ </Text>
90
+ </div>
91
+ <div>
92
+ <Heading tag="h3" variant="h3">{ __( 'Happy browsing!', 'elementor' ) }</Heading>
93
+ <Text>
94
+ <Button
95
+ url="https://go.elementor.com/app-kit-library-how-to-use-kits/"
96
+ target="_blank"
97
+ rel="noreferrer"
98
+ text={ __( 'Learn more', 'elementor' ) }
99
+ color="link"
100
+ onClick={ () => {
101
+ eventTracking( 'kit-library/seek-more-info', 'text link', null, 'info' );
102
+ } }
103
+ />{ ' ' }
104
+ { __( 'about using templates', 'elementor' ) }
105
+ </Text>
106
+ </div>
107
+ </ModalProvider>
108
+ </>
109
+ );
110
+ }
111
+
112
+ IndexHeader.propTypes = {
113
+ refetch: PropTypes.func.isRequired,
114
+ isFetching: PropTypes.bool,
115
+ };
app/modules/kit-library/assets/js/pages/index/index-header.scss ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-kit-library {
2
+ #eps-app-header-btn-refetch {
3
+ padding: 0;
4
+ }
5
+ }
6
+
7
+ .e-kit-library-header-info-modal-container {
8
+ margin-bottom: spacing( 44 );
9
+ }
10
+
11
+ .e-kit-library__tooltip {
12
+ padding: 5px 12px;
13
+ color: $white;
14
+ background-color: $color-gray-anthracite;
15
+ font-size: 10px;
16
+
17
+ &:before {
18
+ border-bottom-color: $color-gray-anthracite;
19
+ }
20
+ }
app/modules/kit-library/assets/js/pages/index/index-sidebar.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { MenuItem } from '@elementor/app-ui';
2
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
3
+
4
+ export default function IndexSidebar( props ) {
5
+ const eventTracking = ( command, category, source, eventType = 'click' ) => appsEventTrackingDispatch(
6
+ command,
7
+ {
8
+ category,
9
+ source,
10
+ element_location: 'app_sidebar',
11
+ event_type: eventType,
12
+ },
13
+ );
14
+
15
+ return (
16
+ <>
17
+ {
18
+ props.menuItems.map( ( item ) => (
19
+ <MenuItem
20
+ key={ item.label }
21
+ text={ item.label }
22
+ className={ `eps-menu-item__link ${ item.isActive ? 'eps-menu-item--active' : '' }` }
23
+ icon={ item.icon }
24
+ url={ item.url }
25
+ onClick={ () => eventTracking( item.trackEventData.command, item.trackEventData.category, 'home page' ) }
26
+ />
27
+ ) )
28
+ }
29
+ { props.tagsFilterSlot }
30
+ </>
31
+ );
32
+ }
33
+
34
+ IndexSidebar.propTypes = {
35
+ tagsFilterSlot: PropTypes.node,
36
+ menuItems: PropTypes.arrayOf( PropTypes.shape( {
37
+ label: PropTypes.string,
38
+ icon: PropTypes.string,
39
+ isActive: PropTypes.bool,
40
+ url: PropTypes.string,
41
+ } ) ),
42
+ };
app/modules/kit-library/assets/js/pages/index/index.js ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Content from '../../../../../../assets/js/layout/content';
2
+ import EnvatoPromotion from '../../components/envato-promotion';
3
+ import ErrorScreen from '../../components/error-screen';
4
+ import FilterIndicationText from '../../components/filter-indication-text';
5
+ import IndexHeader from './index-header';
6
+ import IndexSidebar from './index-sidebar';
7
+ import KitList from '../../components/kit-list';
8
+ import Layout from '../../components/layout';
9
+ import PageLoader from '../../components/page-loader';
10
+ import SearchInput from '../../components/search-input';
11
+ import SortSelect from '../../components/sort-select';
12
+ import TaxonomiesFilter from '../../components/taxonomies-filter';
13
+ import useKits, { defaultQueryParams } from '../../hooks/use-kits';
14
+ import usePageTitle from 'elementor-app/hooks/use-page-title';
15
+ import useTaxonomies from '../../hooks/use-taxonomies';
16
+ import { Grid } from '@elementor/app-ui';
17
+ import { useCallback, useMemo, useEffect } from 'react';
18
+ import { useLastFilterContext } from '../../context/last-filter-context';
19
+ import { useLocation } from '@reach/router';
20
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
21
+
22
+ import './index.scss';
23
+
24
+ /**
25
+ * Generate select and unselect taxonomy functions.
26
+ *
27
+ * @param {Function} setQueryParams
28
+ * @return {((function(*, *): *)|(function(*=): *))[]} taxonomy functions
29
+ */
30
+ function useTaxonomiesSelection( setQueryParams ) {
31
+ const selectTaxonomy = useCallback( ( type, callback ) => setQueryParams(
32
+ ( prev ) => {
33
+ const taxonomies = { ...prev.taxonomies };
34
+
35
+ taxonomies[ type ] = callback( prev.taxonomies[ type ] );
36
+
37
+ return { ...prev, taxonomies };
38
+ },
39
+ ), [ setQueryParams ] );
40
+
41
+ const unselectTaxonomy = useCallback( ( taxonomy ) => setQueryParams( ( prev ) => {
42
+ const taxonomies = Object.entries( prev.taxonomies )
43
+ .reduce( ( current, [ key, groupedTaxonomies ] ) => ( {
44
+ ...current,
45
+ [ key ]: groupedTaxonomies.filter( ( item ) => item !== taxonomy ),
46
+ } ), {} );
47
+
48
+ return { ...prev, taxonomies };
49
+ } ), [ setQueryParams ] );
50
+
51
+ return [ selectTaxonomy, unselectTaxonomy ];
52
+ }
53
+
54
+ /**
55
+ * Generate the menu items for the index page.
56
+ *
57
+ * @param {string} path
58
+ * @return {Array} menu items
59
+ */
60
+ function useMenuItems( path ) {
61
+ return useMemo( () => {
62
+ const page = path.replace( '/', '' );
63
+
64
+ return [
65
+ {
66
+ label: __( 'All Website Kits', 'elementor' ),
67
+ icon: 'eicon-filter',
68
+ isActive: ! page,
69
+ url: '/kit-library',
70
+ trackEventData: { command: 'kit-library/select-organizing-category', category: 'all' },
71
+ },
72
+ {
73
+ label: __( 'Favorites', 'elementor' ),
74
+ icon: 'eicon-heart-o',
75
+ isActive: 'favorites' === page,
76
+ url: '/kit-library/favorites',
77
+ trackEventData: { command: 'kit-library/select-organizing-category', category: 'favorites' },
78
+ },
79
+ ];
80
+ }, [ path ] );
81
+ }
82
+
83
+ /**
84
+ * Update and read the query param from the url
85
+ *
86
+ * @param {*} queryParams
87
+ * @param {*} setQueryParams
88
+ * @param {Array<string>} exclude
89
+ */
90
+ function useRouterQueryParams( queryParams, setQueryParams, exclude = [] ) {
91
+ const location = useLocation(),
92
+ { setLastFilter } = useLastFilterContext();
93
+
94
+ useEffect( () => {
95
+ const filteredQueryParams = Object.fromEntries(
96
+ Object.entries( queryParams )
97
+ .filter( ( [ key, item ] ) => ! exclude.includes( key ) && item ),
98
+ );
99
+
100
+ setLastFilter( filteredQueryParams );
101
+
102
+ history.replaceState(
103
+ null,
104
+ '',
105
+ decodeURI(
106
+ `#${ wp.url.addQueryArgs( location.pathname.split( '?' )[ 0 ] || '/', filteredQueryParams ) }`,
107
+ ),
108
+ );
109
+ }, [ queryParams ] );
110
+
111
+ useEffect( () => {
112
+ const routerQueryParams = Object.keys( defaultQueryParams ).reduce( ( current, key ) => {
113
+ // TODO: Replace with `wp.url.getQueryArgs` when WordPress 5.7 is the min version
114
+ const queryArg = wp.url.getQueryArg( location.pathname, key );
115
+
116
+ if ( ! queryArg ) {
117
+ return current;
118
+ }
119
+
120
+ return {
121
+ ...current,
122
+ [ key ]: queryArg,
123
+ };
124
+ }, {} );
125
+
126
+ setQueryParams( ( prev ) => ( {
127
+ ...prev,
128
+ ...routerQueryParams,
129
+ taxonomies: {
130
+ ...prev.taxonomies,
131
+ ...routerQueryParams.taxonomies,
132
+ },
133
+ ready: true,
134
+ } ) );
135
+ }, [] );
136
+ }
137
+
138
+ export default function Index( props ) {
139
+ usePageTitle( {
140
+ title: __( 'Kit Library', 'elementor' ),
141
+ } );
142
+
143
+ const menuItems = useMenuItems( props.path );
144
+
145
+ const {
146
+ data,
147
+ isSuccess,
148
+ isLoading,
149
+ isFetching,
150
+ isError,
151
+ queryParams,
152
+ setQueryParams,
153
+ clearQueryParams,
154
+ forceRefetch,
155
+ isFilterActive,
156
+ } = useKits( props.initialQueryParams );
157
+
158
+ useRouterQueryParams( queryParams, setQueryParams, [ 'ready', ...Object.keys( props.initialQueryParams ) ] );
159
+
160
+ const {
161
+ data: taxonomiesData,
162
+ forceRefetch: forceRefetchTaxonomies,
163
+ isFetching: isFetchingTaxonomies,
164
+ } = useTaxonomies();
165
+
166
+ const [ selectTaxonomy, unselectTaxonomy ] = useTaxonomiesSelection( setQueryParams );
167
+
168
+ const eventTracking = ( command, elementPosition, search = null, direction = null, sortType = null, action = null, eventType = 'click' ) => {
169
+ appsEventTrackingDispatch(
170
+ command,
171
+ {
172
+ page_source: 'home page',
173
+ element_position: elementPosition,
174
+ search_term: search,
175
+ sort_direction: direction,
176
+ sort_type: sortType,
177
+ event_type: eventType,
178
+ action,
179
+ },
180
+ );
181
+ };
182
+
183
+ return (
184
+ <Layout
185
+ sidebar={
186
+ <IndexSidebar
187
+ tagsFilterSlot={ <TaxonomiesFilter
188
+ selected={ queryParams.taxonomies }
189
+ onSelect={ selectTaxonomy }
190
+ taxonomies={ taxonomiesData }
191
+ category={ props.path }
192
+ /> }
193
+ menuItems={ menuItems }
194
+ />
195
+ }
196
+ header={
197
+ <IndexHeader
198
+ refetch={ () => {
199
+ forceRefetch();
200
+ forceRefetchTaxonomies();
201
+ } }
202
+ isFetching={ isFetching || isFetchingTaxonomies }
203
+ />
204
+ }
205
+ >
206
+ <div className="e-kit-library__index-layout-container">
207
+ <Grid container className="e-kit-library__index-layout-top-area">
208
+ <Grid item className="e-kit-library__index-layout-top-area-search">
209
+ <SearchInput
210
+ // eslint-disable-next-line @wordpress/i18n-ellipsis
211
+ placeholder={ __( 'Search all Website Kits...', 'elementor' ) }
212
+ value={ queryParams.search }
213
+ onChange={ ( value ) => {
214
+ setQueryParams( ( prev ) => ( { ...prev, search: value } ) );
215
+ eventTracking( 'kit-library/kit-free-search', 'top_area_search', value, null, null, null, 'search' );
216
+ } }
217
+ />
218
+ { isFilterActive && <FilterIndicationText
219
+ queryParams={ queryParams }
220
+ resultCount={ data.length || 0 }
221
+ onClear={ clearQueryParams }
222
+ onRemoveTag={ unselectTaxonomy }
223
+ /> }
224
+ </Grid>
225
+ <Grid
226
+ item
227
+ className="e-kit-library__index-layout-top-area-sort"
228
+ >
229
+ <SortSelect
230
+ options={ [
231
+ {
232
+ label: __( 'Featured', 'elementor' ),
233
+ value: 'featuredIndex',
234
+ defaultOrder: 'asc',
235
+ orderDisabled: true,
236
+ },
237
+ {
238
+ label: __( 'New', 'elementor' ),
239
+ value: 'createdAt',
240
+ defaultOrder: 'desc',
241
+ },
242
+ {
243
+ label: __( 'Popular', 'elementor' ),
244
+ value: 'popularityIndex',
245
+ defaultOrder: 'desc',
246
+ },
247
+ {
248
+ label: __( 'Trending', 'elementor' ),
249
+ value: 'trendIndex',
250
+ defaultOrder: 'desc',
251
+ },
252
+ ] }
253
+ value={ queryParams.order }
254
+ onChange={ ( order ) => setQueryParams( ( prev ) => ( { ...prev, order } ) ) }
255
+ onChangeSortDirection={ ( direction ) => eventTracking( 'kit-library/change-sort-direction', 'top_area_sort', null, direction ) }
256
+ onChangeSortValue={ ( value ) => eventTracking( 'kit-library/change-sort-value', 'top_area_sort', null, null, value ) }
257
+ onSortSelectOpen={ () => eventTracking( 'kit-library/change-sort-type', 'top_area_sort', null, null, null, 'expand' ) }
258
+ />
259
+ </Grid>
260
+ </Grid>
261
+ <Content className="e-kit-library__index-layout-main">
262
+ <>
263
+ { isLoading && <PageLoader /> }
264
+ {
265
+ isError && <ErrorScreen
266
+ title={ __( 'Something went wrong.', 'elementor' ) }
267
+ description={ __( 'Nothing to worry about, use 🔄 on the top right to try again. If the problem continues, head over to the Help Center.', 'elementor' ) }
268
+ button={ {
269
+ text: __( 'Learn More', 'elementor' ),
270
+ url: 'http://go.elementor.com/app-kit-library-error/',
271
+ target: '_blank',
272
+ } }
273
+ />
274
+ }
275
+ { isSuccess && 0 < data.length && queryParams.ready && <KitList data={ data } queryParams={ queryParams } source={ props.path } /> }
276
+ {
277
+ isSuccess && 0 === data.length && queryParams.ready && props.renderNoResultsComponent( {
278
+ defaultComponent: <ErrorScreen
279
+ title={ __( 'No results matched your search.', 'elementor' ) }
280
+ description={ __( 'Try different keywords or ', 'elementor' ) }
281
+ button={ {
282
+ text: __( 'Continue browsing.', 'elementor' ),
283
+ action: clearQueryParams,
284
+ category: props.path,
285
+ } }
286
+ />,
287
+ isFilterActive,
288
+ } )
289
+ }
290
+ <EnvatoPromotion category={ props.path } />
291
+ </>
292
+ </Content>
293
+ </div>
294
+ </Layout>
295
+ );
296
+ }
297
+
298
+ Index.propTypes = {
299
+ path: PropTypes.string,
300
+ initialQueryParams: PropTypes.object,
301
+ renderNoResultsComponent: PropTypes.func,
302
+ };
303
+
304
+ Index.defaultProps = {
305
+ initialQueryParams: {},
306
+ renderNoResultsComponent: ( { defaultComponent } ) => defaultComponent,
307
+ };
app/modules/kit-library/assets/js/pages/index/index.scss ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-kit-library-sticky-search-z-index: 2;
2
+
3
+ .e-kit-library {
4
+ &__index-layout-container {
5
+ flex-grow: 1;
6
+ overflow-y: auto;
7
+ display: flex;
8
+ flex-direction: column;
9
+ }
10
+
11
+ &__index-layout-top-area {
12
+ padding: spacing(30) spacing(44);
13
+ position: sticky;
14
+ top: -1px;
15
+ width: 100%;
16
+ z-index: $e-kit-library-sticky-search-z-index;
17
+ background-color: var(--app-background-color);
18
+ gap: spacing( 24 );
19
+
20
+ &-search, &-sort {
21
+ min-width: 265px;
22
+ }
23
+
24
+ &-search {
25
+ flex: 1;
26
+ }
27
+ }
28
+
29
+ &__index-layout-main {
30
+ padding-top: spacing(0);
31
+ padding-bottom: spacing(24);
32
+ overflow-y: hidden;
33
+ height: auto;
34
+ flex: 1 0 auto;
35
+ display: flex;
36
+ flex-direction: column;
37
+ justify-content: space-between;
38
+ }
39
+ }
app/modules/kit-library/assets/js/pages/overview/overview-content-group-item.js ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Document from '../../models/document';
2
+ import { Button, Card, CardBody, CardOverlay, CardHeader, CardImage, Heading } from '@elementor/app-ui';
3
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
4
+
5
+ export default function OverviewContentGroupItem( props ) {
6
+ const eventTracking = ( command, eventType = 'click' ) => {
7
+ appsEventTrackingDispatch(
8
+ command,
9
+ {
10
+ kit_name: props.kitTitle,
11
+ document_type: props.groupData.id,
12
+ document_name: `${ props.groupData.label }-${ props.document.title }`,
13
+ page_source: 'overview',
14
+ element_position: 'content_overview',
15
+ event_type: eventType,
16
+ },
17
+ );
18
+ };
19
+
20
+ return (
21
+ <Card>
22
+ <CardHeader>
23
+ <Heading
24
+ tag="h3"
25
+ title={ props.document.title }
26
+ variant="h5"
27
+ className="eps-card__headline"
28
+ >
29
+ { props.document.title }
30
+ </Heading>
31
+ </CardHeader>
32
+ <CardBody>
33
+ <CardImage alt={ props.document.title } src={ props.document.thumbnailUrl || '' }>
34
+ { props.document.previewUrl && <CardOverlay>
35
+ <Button
36
+ className="e-kit-library__kit-item-overlay-overview-button"
37
+ text={ __( 'View Demo', 'elementor' ) }
38
+ icon="eicon-preview-medium"
39
+ url={ `/kit-library/preview/${ props.kitId }?document_id=${ props.document.id }` }
40
+ onClick={ () => eventTracking( 'kit-library/view-demo-part' ) }
41
+ />
42
+ </CardOverlay> }
43
+ </CardImage>
44
+ </CardBody>
45
+ </Card>
46
+ );
47
+ }
48
+
49
+ OverviewContentGroupItem.propTypes = {
50
+ document: PropTypes.instanceOf( Document ).isRequired,
51
+ kitId: PropTypes.string.isRequired,
52
+ kitTitle: PropTypes.string.isRequired,
53
+ groupData: PropTypes.shape( {
54
+ label: PropTypes.string,
55
+ id: PropTypes.string,
56
+ } ).isRequired,
57
+ };
app/modules/kit-library/assets/js/pages/overview/overview-content-group.js ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ContentType from '../../models/content-type';
2
+ import OverviewContentGroupItem from './overview-content-group-item';
3
+ import { Heading, CssGrid } from '@elementor/app-ui';
4
+
5
+ export default function OverviewContentGroup( props ) {
6
+ if ( props.contentType?.documents?.length <= 0 ) {
7
+ return '';
8
+ }
9
+
10
+ return (
11
+ <div className="e-kit-library__content-overview-group-item">
12
+ <Heading tag="h3" variant="h3" className="e-kit-library__content-overview-group-title">
13
+ { props.contentType.label }
14
+ </Heading>
15
+ <CssGrid spacing={ 24 } colMinWidth={ 250 }>
16
+ { props.contentType.documents.map( ( document ) => {
17
+ return <OverviewContentGroupItem key={ document.id } document={ document } kitId={ props.kitId } kitTitle={ props.kitTitle } groupData={ props.contentType } />;
18
+ } ) }
19
+ </CssGrid>
20
+ </div>
21
+ );
22
+ }
23
+
24
+ OverviewContentGroup.propTypes = {
25
+ contentType: PropTypes.instanceOf( ContentType ),
26
+ kitId: PropTypes.string.isRequired,
27
+ kitTitle: PropTypes.string.isRequired,
28
+ };
app/modules/kit-library/assets/js/pages/overview/overview-sidebar.js ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Collapse from '../../components/collapse';
2
+ import ContentType from '../../models/content-type';
3
+ import FavoritesActions from '../../components/favorites-actions';
4
+ import Kit from '../../models/kit';
5
+ import OverviewTaxonomyBadge from './overview-taxonomy-badge';
6
+ import { Heading, CardImage, Text, Grid } from '@elementor/app-ui';
7
+ import { useState } from 'react';
8
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
9
+
10
+ import './overview-sidebar.scss';
11
+
12
+ export default function OverviewSidebar( props ) {
13
+ const [ isTagsCollapseOpen, setIsTagsCollapseOpen ] = useState( true );
14
+ const [ isInformationCollapseOpen, setIsInformationCollapseOpen ] = useState( false );
15
+ const eventTracking = ( command, section = null, kitName = null, tag = null, isCollapsed = null, eventType = 'click' ) => {
16
+ const action = isCollapsed && isCollapsed ? 'collapse' : 'expand';
17
+ if ( 'boolean' === typeof isCollapsed ) {
18
+ command = `kit-library/${ action }`;
19
+ }
20
+ appsEventTrackingDispatch(
21
+ command,
22
+ {
23
+ page_source: 'overview',
24
+ element_location: 'app_sidebar',
25
+ kit_name: kitName,
26
+ tag,
27
+ section,
28
+ event_type: eventType,
29
+ },
30
+ );
31
+ };
32
+
33
+ return (
34
+ <div className="e-kit-library__item-sidebar">
35
+ <div className="e-kit-library__item-sidebar-header">
36
+ <Heading
37
+ tag="h1"
38
+ variant="h5"
39
+ className="e-kit-library__item-sidebar-header-title">
40
+ { props.model.title }
41
+ </Heading>
42
+ <FavoritesActions
43
+ isFavorite={ props.model.isFavorite }
44
+ id={ props.model.id }
45
+ />
46
+ </div>
47
+ <CardImage
48
+ className="e-kit-library__item-sidebar-thumbnail"
49
+ alt={ props.model.title }
50
+ src={ props.model.thumbnailUrl || '' }
51
+ />
52
+ <Text className="e-kit-library__item-sidebar-description">{ props.model.description || '' }</Text>
53
+ {
54
+ props.model.taxonomies.length > 0 &&
55
+ <Collapse
56
+ isOpen={ isTagsCollapseOpen }
57
+ onChange={ setIsTagsCollapseOpen }
58
+ title={ __( 'TAGS', 'elementor' ) }
59
+ className="e-kit-library__item-sidebar-collapse-tags"
60
+ onClick={ ( collapseState, title ) => {
61
+ eventTracking( null, title, null, null, collapseState );
62
+ } }
63
+ >
64
+ <Grid container className="e-kit-library__item-sidebar-tags-container">
65
+ { props.model.taxonomies.map( ( taxonomy ) => (
66
+ <OverviewTaxonomyBadge
67
+ key={ taxonomy }
68
+ onClick={ ( taxonomyText ) => {
69
+ eventTracking( 'kit-library/filter', null, props.model.title, taxonomyText );
70
+ } }
71
+ >{ taxonomy }</OverviewTaxonomyBadge>
72
+ ) ) }
73
+ </Grid>
74
+ </Collapse>
75
+ }
76
+ {
77
+ props.groupedKitContent?.length > 0 && props.model.documents.length > 0 &&
78
+ <Collapse
79
+ isOpen={ isInformationCollapseOpen }
80
+ onChange={ setIsInformationCollapseOpen }
81
+ title={ __( 'WHAT\'S INSIDE', 'elementor' ) }
82
+ className="e-kit-library__item-sidebar-collapse-info"
83
+ onClick={ ( collapseState, title ) => {
84
+ eventTracking( null, title, null, null, collapseState );
85
+ } }
86
+ >
87
+ {
88
+ props.groupedKitContent.map( ( contentType ) => {
89
+ if ( contentType.documents <= 0 ) {
90
+ return '';
91
+ }
92
+
93
+ return (
94
+ <Text className="e-kit-library__item-information-text" key={ contentType.id }>
95
+ { contentType.documents.length } { contentType.label }
96
+ </Text>
97
+ );
98
+ } )
99
+ }
100
+ </Collapse>
101
+ }
102
+ </div>
103
+ );
104
+ }
105
+
106
+ OverviewSidebar.propTypes = {
107
+ model: PropTypes.instanceOf( Kit ).isRequired,
108
+ index: PropTypes.number,
109
+ groupedKitContent: PropTypes.arrayOf(
110
+ PropTypes.instanceOf( ContentType ),
111
+ ),
112
+ };
app/modules/kit-library/assets/js/pages/overview/overview-sidebar.scss ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .#{$root} {
2
+ --e-kit-library-item-sidebar-header-title-color: #{tints(600)};
3
+ --e-kit-library-item-sidebar-description-color: #{tints(600)};
4
+ --e-kit-library-item-information-text-color: #{tints(600)};
5
+ }
6
+
7
+ .eps-theme-dark {
8
+ .#{$root} {
9
+ --e-kit-library-item-sidebar-header-title-color: #{dark-tints(200)};
10
+ --e-kit-library-item-sidebar-description-color: #{dark-tints(200)};
11
+ --e-kit-library-item-information-text-color: #{dark-tints(200)};
12
+ }
13
+ }
14
+
15
+ .e-kit-library {
16
+ &__item-sidebar {
17
+ padding: spacing(24) spacing(30);
18
+ }
19
+
20
+ &__item-sidebar-header {
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: space-between;
24
+ }
25
+
26
+ &__item-sidebar-header-title {
27
+ color: var(--e-kit-library-item-sidebar-header-title-color);
28
+ margin-bottom: 0;
29
+ }
30
+
31
+ &__item-sidebar-thumbnail {
32
+ margin-top: spacing(24);
33
+ box-shadow: shadow("2");
34
+ }
35
+
36
+ &__item-sidebar-description {
37
+ margin-top: spacing(24);
38
+ color: var(--e-kit-library-item-sidebar-description-color);
39
+ }
40
+
41
+ &__item-sidebar-collapse-tags {
42
+ margin-top: spacing(44);
43
+ }
44
+
45
+ &__item-sidebar-collapse-info {
46
+ margin-top: spacing(30);
47
+ }
48
+
49
+ &__item-sidebar-tags-container {
50
+ gap: spacing(10);
51
+ }
52
+
53
+ &__item-information-text {
54
+ font-size: type(size, "13");
55
+ color: var(--e-kit-library-item-information-text-color);
56
+ margin-bottom: spacing(5);
57
+ }
58
+ }
app/modules/kit-library/assets/js/pages/overview/overview-taxonomy-badge.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Badge from '../../components/badge';
2
+ import useTaxonomies from '../../hooks/use-taxonomies';
3
+ import { Link } from '@reach/router';
4
+ import { useMemo } from 'react';
5
+
6
+ export default function OverviewTaxonomyBadge( props ) {
7
+ const { data } = useTaxonomies();
8
+
9
+ const taxonomyText = props.children;
10
+
11
+ const type = useMemo(
12
+ () => {
13
+ if ( ! data ) {
14
+ return null;
15
+ }
16
+
17
+ return data.find( ( item ) => item.text === taxonomyText )?.type;
18
+ },
19
+ [ data, taxonomyText ],
20
+ );
21
+
22
+ if ( ! type ) {
23
+ return '';
24
+ }
25
+
26
+ return (
27
+ <Link
28
+ onClick={ () => {
29
+ props?.onClick( taxonomyText );
30
+ } }
31
+ to={ `/kit-library?taxonomies[${ type }][]=${ taxonomyText }` }
32
+ >
33
+ <Badge>
34
+ { props.children }
35
+ </Badge>
36
+ </Link>
37
+ );
38
+ }
39
+
40
+ OverviewTaxonomyBadge.propTypes = {
41
+ children: PropTypes.string,
42
+ onClick: PropTypes.func,
43
+ };
app/modules/kit-library/assets/js/pages/overview/overview.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Content from 'elementor-app/layout/content';
2
+ import ElementorLoading from 'elementor-app/molecules/elementor-loading';
3
+ import ItemHeader from '../../components/item-header';
4
+ import Layout from '../../components/layout';
5
+ import OverviewContentGroup from './overview-content-group';
6
+ import OverviewSidebar from './overview-sidebar';
7
+ import useKit from '../../hooks/use-kit';
8
+ import useKitDocumentByType from '../../hooks/use-kit-document-by-type';
9
+ import usePageTitle from 'elementor-app/hooks/use-page-title';
10
+ import { useMemo } from 'react';
11
+ import { useNavigate } from '@reach/router';
12
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
13
+
14
+ import './overview.scss';
15
+
16
+ function useHeaderButtons( id, kitName ) {
17
+ const navigate = useNavigate();
18
+
19
+ return useMemo( () => [
20
+ {
21
+ id: 'view-demo',
22
+ text: __( 'View Demo', 'elementor' ),
23
+ hideText: false,
24
+ variant: 'outlined',
25
+ color: 'secondary',
26
+ size: 'sm',
27
+ onClick: () => {
28
+ appsEventTrackingDispatch(
29
+ 'kit-library/view-demo-page',
30
+ {
31
+ kit_name: kitName,
32
+ page_source: 'overview',
33
+ element_position: 'app_header',
34
+ view_type_clicked: 'demo',
35
+ },
36
+ );
37
+ navigate( `/kit-library/preview/${ id }` );
38
+ },
39
+ includeHeaderBtnClass: false,
40
+ },
41
+ ], [ id ] );
42
+ }
43
+
44
+ export default function Overview( props ) {
45
+ const { data: kit, isError, isLoading } = useKit( props.id );
46
+ const { data: documentsByType } = useKitDocumentByType( kit );
47
+ const headerButtons = useHeaderButtons( props.id, kit && kit.title );
48
+
49
+ usePageTitle( {
50
+ title: kit
51
+ ? `${ __( 'Kit Library', 'elementor' ) } | ${ kit.title }`
52
+ // eslint-disable-next-line @wordpress/i18n-ellipsis
53
+ : __( 'Loading...', 'elementor' ),
54
+ } );
55
+
56
+ if ( isError ) {
57
+ // Will be caught by the App error boundary.
58
+ throw new Error();
59
+ }
60
+
61
+ if ( isLoading ) {
62
+ return <ElementorLoading />;
63
+ }
64
+
65
+ return (
66
+ <Layout
67
+ header={ <ItemHeader model={ kit } buttons={ headerButtons } pageId="overview" /> }
68
+ sidebar={ <OverviewSidebar model={ kit } groupedKitContent={ documentsByType } /> }
69
+ >
70
+ {
71
+ documentsByType.length > 0 &&
72
+ <Content>
73
+ {
74
+ documentsByType.map( ( contentType ) => (
75
+ <OverviewContentGroup
76
+ key={ contentType.id }
77
+ contentType={ contentType }
78
+ kitId={ props.id }
79
+ kitTitle={ kit.title }
80
+ />
81
+ ) )
82
+ }
83
+ </Content>
84
+ }
85
+ </Layout>
86
+ );
87
+ }
88
+
89
+ Overview.propTypes = {
90
+ id: PropTypes.string,
91
+ };
app/modules/kit-library/assets/js/pages/overview/overview.scss ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $root: e-kit-library;
2
+
3
+ .#{$root} {
4
+ --e-kit-library-content-overview-group-title-color: #{tints(600)};
5
+ }
6
+
7
+ .eps-theme-dark {
8
+ .#{$root} {
9
+ --e-kit-library-content-overview-group-title-color: #{dark-tints(200)};
10
+ }
11
+ }
12
+
13
+ .e-kit-library {
14
+ &__content-overview-group-item {
15
+ margin-bottom: spacing(44);
16
+ }
17
+
18
+ &__content-overview-group-title {
19
+ margin-bottom: spacing(30);
20
+ color: var(--e-kit-library-content-overview-group-title-color);
21
+ }
22
+ }
app/modules/kit-library/assets/js/pages/preview/preview-iframe.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable jsx-a11y/iframe-has-title */
2
+ import { useRef, useEffect } from 'react';
3
+ import { Grid } from '@elementor/app-ui';
4
+
5
+ export function PreviewIframe( props ) {
6
+ const ref = useRef();
7
+
8
+ useEffect( () => {
9
+ if ( ! ref.current ) {
10
+ return;
11
+ }
12
+
13
+ const listener = () => props.onLoaded();
14
+
15
+ ref.current.addEventListener( 'load', listener );
16
+
17
+ return () => ref.current && ref.current.removeEventListener( 'load', listener );
18
+ }, [ ref.current, props.previewUrl ] );
19
+
20
+ return (
21
+ <Grid container justify="center" className="e-kit-library__preview-iframe-container">
22
+ <iframe
23
+ className="e-kit-library__preview-iframe"
24
+ src={ props.previewUrl }
25
+ style={ props.style }
26
+ ref={ ref }
27
+ />
28
+ </Grid>
29
+ );
30
+ }
31
+
32
+ PreviewIframe.propTypes = {
33
+ previewUrl: PropTypes.string.isRequired,
34
+ style: PropTypes.object,
35
+ onLoaded: PropTypes.func,
36
+ };
37
+
38
+ PreviewIframe.defaultProps = {
39
+ style: {
40
+ width: '100%',
41
+ height: '100%',
42
+ },
43
+ onLoaded: () => {},
44
+ };
app/modules/kit-library/assets/js/pages/preview/preview-iframe.scss ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ .e-kit-library__preview-library {
2
+ border: none;
3
+ transition: $transition-hover;
4
+ box-shadow: shadow("2");
5
+
6
+ &-container {
7
+ overflow-y: auto;
8
+ }
9
+ }
app/modules/kit-library/assets/js/pages/preview/preview-responsive-controls.js ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { breakpoints } from './preview';
2
+ import { Button, Grid } from '@elementor/app-ui';
3
+
4
+ import './preview-responsive-controls.scss';
5
+
6
+ export default function PreviewResponsiveControls( props ) {
7
+ return (
8
+ <Grid container alignItems="center" justify="center" className="e-kit-library__preview-responsive-controls">
9
+ { breakpoints.map( ( { label, value } ) => {
10
+ let className = 'e-kit-library__preview-responsive-controls-item';
11
+
12
+ if ( props.active === value ) {
13
+ className += ' e-kit-library__preview-responsive-controls-item--active';
14
+ }
15
+
16
+ return (
17
+ <Button
18
+ key={ value }
19
+ text={ label }
20
+ hideText={ true }
21
+ className={ className }
22
+ icon={ `eicon-device-${ value }` }
23
+ onClick={ () => props.onChange( value ) }
24
+ />
25
+ );
26
+ } ) }
27
+ </Grid>
28
+ );
29
+ }
30
+
31
+ PreviewResponsiveControls.propTypes = {
32
+ active: PropTypes.string,
33
+ onChange: PropTypes.func.isRequired,
34
+ };
35
+
36
+ PreviewResponsiveControls.defaultProps = {
37
+ active: 'desktop',
38
+ };
app/modules/kit-library/assets/js/pages/preview/preview-responsive-controls.scss ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $root: e-kit-library__preview-responsive-controls;
2
+
3
+ .#{$root} {
4
+ width: auto;
5
+
6
+ &-item {
7
+ margin: 0 spacing(5);
8
+ color: tints(500);
9
+ padding: spacing(5);
10
+
11
+ &:hover {
12
+ background: rgba( theme-colors( 'info' ), $opacity-01 );
13
+ border-radius: 3px;
14
+ transition: $transition-hover;
15
+ }
16
+
17
+ &--active {
18
+ color: theme-colors( 'info' );
19
+ }
20
+ }
21
+ }
app/modules/kit-library/assets/js/pages/preview/preview.js ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ElementorLoading from 'elementor-app/molecules/elementor-loading';
2
+ import ItemHeader from '../../components/item-header';
3
+ import Layout from '../../components/layout';
4
+ import PageLoader from '../../components/page-loader';
5
+ import PreviewResponsiveControls from './preview-responsive-controls';
6
+ import useKit from '../../hooks/use-kit';
7
+ import usePageTitle from 'elementor-app/hooks/use-page-title';
8
+ import { PreviewIframe } from './preview-iframe';
9
+ import { useLocation, useNavigate } from '@reach/router';
10
+ import { useState, useMemo } from 'react';
11
+ import { appsEventTrackingDispatch } from 'elementor-app/event-track/apps-event-tracking';
12
+
13
+ import './preview.scss';
14
+
15
+ export const breakpoints = [
16
+ {
17
+ value: 'desktop',
18
+ label: __( 'Desktop', 'elementor' ),
19
+ style: {
20
+ width: '100%',
21
+ height: '100%',
22
+ },
23
+ },
24
+ {
25
+ value: 'tablet',
26
+ label: __( 'Tablet', 'elementor' ),
27
+ style: {
28
+ marginTop: '30px',
29
+ marginBottom: '30px',
30
+ width: '768px',
31
+ height: '1024px',
32
+ },
33
+ },
34
+ {
35
+ value: 'mobile',
36
+ label: __( 'Mobile', 'elementor' ),
37
+ style: {
38
+ marginTop: '30px',
39
+ marginBottom: '30px',
40
+ width: '375px',
41
+ height: '667px',
42
+ },
43
+ },
44
+ ];
45
+
46
+ function useHeaderButtons( id, kitName ) {
47
+ const navigate = useNavigate();
48
+ const eventTracking = ( command, viewTypeClicked, eventType = 'click' ) => {
49
+ appsEventTrackingDispatch(
50
+ command,
51
+ {
52
+ kit_name: kitName,
53
+ element_position: 'app_header',
54
+ page_source: 'view demo',
55
+ view_type_clicked: viewTypeClicked,
56
+ event_type: eventType,
57
+ },
58
+ );
59
+ };
60
+ return useMemo( () => [
61
+ {
62
+ id: 'overview',
63
+ text: __( 'Overview', 'elementor' ),
64
+ hideText: false,
65
+ variant: 'outlined',
66
+ color: 'secondary',
67
+ size: 'sm',
68
+ onClick: () => {
69
+ eventTracking( 'kit-library/view-overview-page', 'overview' );
70
+ navigate( `/kit-library/overview/${ id }` );
71
+ },
72
+ includeHeaderBtnClass: false,
73
+ },
74
+ ], [ id ] );
75
+ }
76
+
77
+ /**
78
+ * Get preview url.
79
+ *
80
+ * @param {*} data
81
+ * @return {null|string} Preview URL
82
+ */
83
+ function usePreviewUrl( data ) {
84
+ const location = useLocation();
85
+
86
+ return useMemo( () => {
87
+ if ( ! data ) {
88
+ return null;
89
+ }
90
+
91
+ const documentId = new URLSearchParams( location.pathname.split( '?' )?.[ 1 ] ).get( 'document_id' ),
92
+ utm = '?utm_source=kit-library&utm_medium=wp-dash&utm_campaign=preview',
93
+ previewUrl = data.previewUrl ? data.previewUrl + utm : data.previewUrl;
94
+
95
+ if ( ! documentId ) {
96
+ return previewUrl;
97
+ }
98
+
99
+ const documentPreviewUrl = data.documents.find( ( item ) => item.id === parseInt( documentId ) )?.previewUrl || previewUrl;
100
+ return documentPreviewUrl ? documentPreviewUrl + utm : documentPreviewUrl;
101
+ }, [ location, data ] );
102
+ }
103
+
104
+ export default function Preview( props ) {
105
+ const { data, isError, isLoading } = useKit( props.id );
106
+ const [ isIframeLoading, setIsIframeLoading ] = useState( true );
107
+ const headersButtons = useHeaderButtons( props.id, data && data.title );
108
+ const previewUrl = usePreviewUrl( data );
109
+ const [ activeDevice, setActiveDevice ] = useState( 'desktop' );
110
+ const iframeStyle = useMemo(
111
+ () => breakpoints.find( ( { value } ) => value === activeDevice ).style,
112
+ [ activeDevice ],
113
+ );
114
+ const eventTracking = ( command, layout, elementPosition = null, eventType = 'click' ) => {
115
+ appsEventTrackingDispatch(
116
+ command,
117
+ {
118
+ kit_name: data.title,
119
+ page_source: 'view demo',
120
+ layout,
121
+ element_position: elementPosition,
122
+ event_type: eventType,
123
+ },
124
+ );
125
+ };
126
+
127
+ const onChange = ( device ) => {
128
+ setActiveDevice( device );
129
+ eventTracking( 'kit-library/responsive-controls', device, 'app_header' );
130
+ };
131
+
132
+ usePageTitle( {
133
+ title: data
134
+ ? `${ __( 'Kit Library', 'elementor' ) } | ${ data.title }`
135
+ // eslint-disable-next-line @wordpress/i18n-ellipsis
136
+ : __( 'Loading...', 'elementor' ),
137
+ } );
138
+
139
+ if ( isError ) {
140
+ // Will be caught by the App error boundary.
141
+ throw new Error();
142
+ }
143
+
144
+ if ( isLoading ) {
145
+ return <ElementorLoading />;
146
+ }
147
+
148
+ return (
149
+ <Layout header={
150
+ <ItemHeader
151
+ model={ data }
152
+ buttons={ headersButtons }
153
+ centerColumn={ <PreviewResponsiveControls active={ activeDevice } onChange={ ( device ) => onChange( device ) } kitName={ data.title } /> }
154
+ pageId="demo"
155
+ />
156
+ }>
157
+ { isIframeLoading && <PageLoader className="e-kit-library__preview-loader" /> }
158
+ {
159
+ previewUrl &&
160
+ <PreviewIframe
161
+ previewUrl={ previewUrl }
162
+ style={ iframeStyle }
163
+ onLoaded={ () => setIsIframeLoading( false ) }
164
+ />
165
+ }
166
+ </Layout>
167
+ );
168
+ }
169
+
170
+ Preview.propTypes = {
171
+ id: PropTypes.string,
172
+ };
173
+
app/modules/kit-library/assets/js/pages/preview/preview.scss ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-kit-library__preview {
2
+ &-loader {
3
+ position: absolute;
4
+ top: 0;
5
+ left: 0;
6
+ z-index: $ground-layer;
7
+ }
8
+
9
+ &-iframe {
10
+ border: none;
11
+ transition: $transition-hover;
12
+ box-shadow: shadow( "2" );
13
+ }
14
+
15
+ &-iframe-container {
16
+ overflow-y: auto;
17
+ position: relative;
18
+ z-index: $first-layer;
19
+ }
20
+ }
app/modules/kit-library/connect/kit-library.php ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Elementor\App\Modules\KitLibrary\Connect;
3
+
4
+ use Elementor\Core\Common\Modules\Connect\Apps\Base_App;
5
+ use Elementor\Core\Common\Modules\Connect\Apps\Library;
6
+
7
+ if ( ! defined( 'ABSPATH' ) ) {
8
+ exit; // Exit if accessed directly
9
+ }
10
+
11
+ class Kit_Library extends Library {
12
+ const DEFAULT_BASE_ENDPOINT = 'https://my.elementor.com/api/v1/kits-library';
13
+ const FALLBACK_BASE_ENDPOINT = 'https://ms-8874.elementor.com/api/v1/kits-library';
14
+
15
+ public function get_title() {
16
+ return __( 'Kit Library', 'elementor' );
17
+ }
18
+
19
+ public function get_all() {
20
+ return $this->http_request( 'GET', 'kits' );
21
+ }
22
+
23
+ public function get_taxonomies() {
24
+ return $this->http_request( 'GET', 'taxonomies' );
25
+ }
26
+
27
+ public function get_manifest( $id ) {
28
+ return $this->http_request( 'GET', "kits/{$id}/manifest" );
29
+ }
30
+
31
+ public function download_link( $id ) {
32
+ return $this->http_request( 'GET', "kits/{$id}/download-link" );
33
+ }
34
+
35
+ protected function get_api_url() {
36
+ return [
37
+ static::DEFAULT_BASE_ENDPOINT,
38
+ static::FALLBACK_BASE_ENDPOINT,
39
+ ];
40
+ }
41
+
42
+ /**
43
+ * Get all the connect information
44
+ *
45
+ * @return array
46
+ */
47
+ protected function get_connect_info() {
48
+ $connect_info = $this->get_base_connect_info();
49
+
50
+ $additional_info = [];
51
+
52
+ // BC Support.
53
+ $old_kit_library = new \Elementor\Core\App\Modules\KitLibrary\Connect\Kit_Library();
54
+
55
+ /**
56
+ * Additional connect info.
57
+ *
58
+ * Filters the connection information when connecting to Elementor servers.
59
+ * This hook can be used to add more information or add more data.
60
+ *
61
+ * @param array $additional_info Additional connecting information array.
62
+ * @param Base_App $this The base app instance.
63
+ */
64
+ $additional_info = apply_filters( 'elementor/connect/additional-connect-info', $additional_info, $old_kit_library );
65
+
66
+ return array_merge( $connect_info, $additional_info );
67
+ }
68
+
69
+ protected function init() {
70
+ // Remove parent init actions.
71
+ }
72
+ }
{core/app → app}/modules/kit-library/data/base-controller.php RENAMED
@@ -1,5 +1,5 @@
1
  <?php
2
- namespace Elementor\Core\App\Modules\KitLibrary\Data;
3
 
4
  use Elementor\Plugin;
5
  use Elementor\Data\V2\Base\Controller;
1
  <?php
2
+ namespace Elementor\App\Modules\KitLibrary\Data;
3
 
4
  use Elementor\Plugin;
5
  use Elementor\Data\V2\Base\Controller;
{core/app → app}/modules/kit-library/data/kits/controller.php RENAMED
@@ -1,7 +1,7 @@
1
  <?php
2
- namespace Elementor\Core\App\Modules\KitLibrary\Data\Kits;
3
 
4
- use Elementor\Core\App\Modules\KitLibrary\Data\Base_Controller;
5
  use Elementor\Data\V2\Base\Exceptions\Error_404;
6
 
7
  if ( ! defined( 'ABSPATH' ) ) {
1
  <?php
2
+ namespace Elementor\App\Modules\KitLibrary\Data\Kits;
3
 
4
+ use Elementor\App\Modules\KitLibrary\Data\Base_Controller;
5
  use Elementor\Data\V2\Base\Exceptions\Error_404;
6
 
7
  if ( ! defined( 'ABSPATH' ) ) {
{core/app → app}/modules/kit-library/data/kits/endpoints/download-link.php RENAMED
@@ -1,8 +1,8 @@
1
  <?php
2
- namespace Elementor\Core\App\Modules\KitLibrary\Data\Kits\Endpoints;
3
 
4
  use Elementor\Data\V2\Base\Endpoint;
5
- use Elementor\Core\App\Modules\KitLibrary\Data\Kits\Controller;
6
 
7
  if ( ! defined( 'ABSPATH' ) ) {
8
  exit; // Exit if accessed directly.
1
  <?php
2
+ namespace Elementor\App\Modules\KitLibrary\Data\Kits\Endpoints;
3
 
4
  use Elementor\Data\V2\Base\Endpoint;
5
+ use Elementor\App\Modules\KitLibrary\Data\Kits\Controller;
6
 
7
  if ( ! defined( 'ABSPATH' ) ) {
8
  exit; // Exit if accessed directly.
{core/app → app}/modules/kit-library/data/kits/endpoints/favorites.php RENAMED
@@ -1,7 +1,7 @@
1
  <?php
2
- namespace Elementor\Core\App\Modules\KitLibrary\Data\Kits\Endpoints;
3
 
4
- use Elementor\Core\App\Modules\KitLibrary\Data\Kits\Controller;
5
  use Elementor\Data\V2\Base\Endpoint;
6
 
7
  if ( ! defined( 'ABSPATH' ) ) {
1
  <?php
2
+ namespace Elementor\App\Modules\KitLibrary\Data\Kits\Endpoints;
3
 
4
+ use Elementor\App\Modules\KitLibrary\Data\Kits\Controller;
5
  use Elementor\Data\V2\Base\Endpoint;
6
 
7
  if ( ! defined( 'ABSPATH' ) ) {
{core/app → app}/modules/kit-library/data/repository.php RENAMED
@@ -1,11 +1,11 @@
1
  <?php
2
- namespace Elementor\Core\App\Modules\KitLibrary\Data;
3
 
4
  use Elementor\Core\Utils\Collection;
5
  use Elementor\Data\V2\Base\Exceptions\Error_404;
6
  use Elementor\Data\V2\Base\Exceptions\WP_Error_Exception;
7
  use Elementor\Modules\Library\User_Favorites;
8
- use Elementor\Core\App\Modules\KitLibrary\Connect\Kit_Library;
9
 
10
  if ( ! defined( 'ABSPATH' ) ) {
11
  exit; // Exit if accessed directly.
1
  <?php
2
+ namespace Elementor\App\Modules\KitLibrary\Data;
3
 
4
  use Elementor\Core\Utils\Collection;
5
  use Elementor\Data\V2\Base\Exceptions\Error_404;
6
  use Elementor\Data\V2\Base\Exceptions\WP_Error_Exception;
7
  use Elementor\Modules\Library\User_Favorites;
8
+ use Elementor\App\Modules\KitLibrary\Connect\Kit_Library;
9
 
10
  if ( ! defined( 'ABSPATH' ) ) {
11
  exit; // Exit if accessed directly.
{core/app → app}/modules/kit-library/data/taxonomies/controller.php RENAMED
@@ -1,7 +1,7 @@
1
  <?php
2
- namespace Elementor\Core\App\Modules\KitLibrary\Data\Taxonomies;
3
 
4
- use Elementor\Core\App\Modules\KitLibrary\Data\Base_Controller;
5
 
6
  if ( ! defined( 'ABSPATH' ) ) {
7
  exit; // Exit if accessed directly.
1
  <?php
2
+ namespace Elementor\App\Modules\KitLibrary\Data\Taxonomies;
3
 
4
+ use Elementor\App\Modules\KitLibrary\Data\Base_Controller;
5
 
6
  if ( ! defined( 'ABSPATH' ) ) {
7
  exit; // Exit if accessed directly.
app/modules/kit-library/module.php ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Elementor\App\Modules\KitLibrary;
3
+
4
+ use Elementor\Core\Admin\Menu\Main as MainMenu;
5
+ use Elementor\Plugin;
6
+ use Elementor\TemplateLibrary\Source_Local;
7
+ use Elementor\Core\Base\Module as BaseModule;
8
+ use Elementor\App\Modules\KitLibrary\Connect\Kit_Library;
9
+ use Elementor\Core\Common\Modules\Connect\Module as ConnectModule;
10
+ use Elementor\App\Modules\KitLibrary\Data\Kits\Controller as Kits_Controller;
11
+ use Elementor\App\Modules\KitLibrary\Data\Taxonomies\Controller as Taxonomies_Controller;
12
+
13
+ if ( ! defined( 'ABSPATH' ) ) {
14
+ exit; // Exit if accessed directly
15
+ }
16
+
17
+ class Module extends BaseModule {
18
+ /**
19
+ * Get name.
20
+ *
21
+ * @access public
22
+ *
23
+ * @return string
24
+ */
25
+ public function get_name() {
26
+ return 'kit-library';
27
+ }
28
+
29
+ private function register_admin_menu( MainMenu $menu ) {
30
+ $menu->add_submenu( [
31
+ 'page_title' => __( 'Kit Library', 'elementor' ),
32
+ 'menu_title' => '<span id="e-admin-menu__kit-library">' . __( 'Kit Library', 'elementor' ) . '</span>',
33
+ 'menu_slug' => Plugin::$instance->app->get_base_url() . '#/kit-library',
34
+ 'index' => 40,
35
+ ] );
36
+ }
37
+
38
+ /**
39
+ * Register the admin menu the old way.
40
+ */
41
+ private function register_admin_menu_legacy() {
42
+ add_submenu_page(
43
+ Source_Local::ADMIN_MENU_SLUG,
44
+ __( 'Kit Library', 'elementor' ),
45
+ __( 'Kit Library', 'elementor' ),
46
+ 'manage_options',
47
+ Plugin::$instance->app->get_base_url() . '#/kit-library'
48
+ );
49
+ }
50
+
51
+ private function set_kit_library_settings() {
52
+ if ( ! Plugin::$instance->common ) {
53
+ return;
54
+ }
55
+
56
+ /** @var ConnectModule $connect */
57
+ $connect = Plugin::$instance->common->get_component( 'connect' );
58
+
59
+ /** @var Kit_Library $kit_library */
60
+ $kit_library = $connect->get_app( 'kit-library' );
61
+
62
+ Plugin::$instance->app->set_settings( 'kit-library', [
63
+ 'has_access_to_module' => current_user_can( 'administrator' ),
64
+ 'subscription_plans' => $connect->get_subscription_plans( 'kit-library' ),
65
+ 'is_pro' => false,
66
+ 'is_library_connected' => $kit_library->is_connected(),
67
+ 'library_connect_url' => $kit_library->get_admin_url( 'authorize', [
68
+ 'utm_source' => 'kit-library',
69
+ 'utm_medium' => 'wp-dash',
70
+ 'utm_campaign' => 'library-connect',
71
+ 'utm_term' => '%%page%%', // Will be replaced in the frontend.
72
+ ] ),
73
+ 'access_level' => ConnectModule::ACCESS_LEVEL_CORE,
74
+ ] );
75
+ }
76
+
77
+ /**
78
+ * Module constructor.
79
+ */
80
+ public function __construct() {
81
+ Plugin::$instance->data_manager_v2->register_controller( new Kits_Controller() );
82
+ Plugin::$instance->data_manager_v2->register_controller( new Taxonomies_Controller() );
83
+
84
+ if ( Plugin::$instance->experiments->is_feature_active( 'admin_menu_rearrangement' ) ) {
85
+ add_action( 'elementor/admin/menu_registered/elementor', function( MainMenu $menu ) {
86
+ $this->register_admin_menu( $menu );
87
+ } );
88
+ } else {
89
+ add_action( 'admin_menu', function() {
90
+ $this->register_admin_menu_legacy();
91
+ }, 50 /* after Elementor page */ );
92
+ }
93
+
94
+ add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) {
95
+ $connect_module->register_app( 'kit-library', Kit_Library::get_class_name() );
96
+ } );
97
+
98
+ add_action( 'elementor/init', function () {
99
+ $this->set_kit_library_settings();
100
+ }, 12 /** after the initiation of the connect kit library */ );
101
+ }
102
+ }
app/modules/onboarding/assets/js/app.js ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect } from 'react';
2
+ import { LocationProvider, Router } from '@reach/router';
3
+ import router from '@elementor/router';
4
+
5
+ import { ContextProvider } from './context/context';
6
+ import Account from './pages/account';
7
+ import HelloTheme from './pages/hello-theme';
8
+ import SiteName from './pages/site-name';
9
+ import SiteLogo from './pages/site-logo';
10
+ import GoodToGo from './pages/good-to-go';
11
+ import InstallPro from './pages/upload-and-install-pro';
12
+
13
+ export default function App() {
14
+ // Send an AJAX request to update the database option which makes sure the Onboarding process only runs once,
15
+ // for new Elementor sites.
16
+ useEffect( () => {
17
+ if ( ! elementorAppConfig.onboarding.onboardingAlreadyRan ) {
18
+ const formData = new FormData();
19
+
20
+ formData.append( '_nonce', elementorCommon.config.ajax.nonce );
21
+ formData.append( 'action', 'elementor_update_onboarding_option' );
22
+
23
+ fetch( elementorCommon.config.ajax.url, {
24
+ method: 'POST',
25
+ body: formData,
26
+ } );
27
+ }
28
+
29
+ elementorAppConfig.return_url = elementorAppConfig.admin_url;
30
+ }, [] );
31
+
32
+ return (
33
+ <ContextProvider>
34
+ <LocationProvider history={ router.appHistory }>
35
+ <Router>
36
+ <Account default />
37
+ <HelloTheme path="hello" />
38
+ <SiteName path="siteName" />
39
+ <SiteLogo path="siteLogo" />
40
+ <GoodToGo path="goodToGo" />
41
+ <InstallPro path="uploadAndInstallPro" />
42
+ </Router>
43
+ </LocationProvider>
44
+ </ContextProvider>
45
+ );
46
+ }
app/modules/onboarding/assets/js/components/button.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function Button( props ) {
2
+ const { buttonSettings, type } = props;
3
+
4
+ let buttonClasses = 'e-onboarding__button';
5
+
6
+ if ( type ) {
7
+ buttonClasses += ` e-onboarding__button-${ type }`;
8
+ }
9
+
10
+ if ( buttonSettings.className ) {
11
+ buttonSettings.className += ' ' + buttonClasses;
12
+ } else {
13
+ buttonSettings.className = buttonClasses;
14
+ }
15
+
16
+ if ( buttonSettings.href ) {
17
+ return <a { ...buttonSettings }>{ buttonSettings.text }</a>;
18
+ }
19
+
20
+ return <div { ...buttonSettings }>{ buttonSettings.text }</div>;
21
+ }
22
+
23
+ Button.propTypes = {
24
+ buttonSettings: PropTypes.object.isRequired,
25
+ type: PropTypes.string,
26
+ };
app/modules/onboarding/assets/js/components/button.scss ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-onboarding__button {
2
+ font-size: 18px;
3
+ cursor: pointer;
4
+
5
+ &-action {
6
+ color: $platform-text;
7
+ background-color: $platform-primary;
8
+ width: 325px;
9
+ padding: 15px 15px;
10
+ text-align: center;
11
+ }
12
+
13
+ &-skip {
14
+ color: $platform-secondary;
15
+ padding: 15px 29px;
16
+ }
17
+
18
+ &--disabled {
19
+ pointer-events: none;
20
+ background-color: $platform-gray-lightest;
21
+ color: $platform-gray-light;
22
+
23
+ &:hover {
24
+ cursor: progress;
25
+ }
26
+ }
27
+
28
+ &--processing {
29
+ pointer-events: none;
30
+ filter: brightness(90%);
31
+ }
32
+ }
app/modules/onboarding/assets/js/components/card.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function Card( { image, imageAlt, text, link, name, clickAction } ) {
2
+ const onClick = () => {
3
+ elementorCommon.events.dispatchEvent( {
4
+ event: 'starting canvas click',
5
+ version: '',
6
+ details: {
7
+ placement: elementorAppConfig.onboarding.eventPlacement,
8
+ selection: name,
9
+ },
10
+ } );
11
+
12
+ if ( clickAction ) {
13
+ clickAction();
14
+ }
15
+ };
16
+
17
+ return (
18
+ <a className="e-onboarding__card" href={ link } onClick={ onClick }>
19
+ <img className="e-onboarding__card-image" src={ image } alt={ imageAlt } />
20
+ <div className="e-onboarding__card-text">{ text }</div>
21
+ </a>
22
+ );
23
+ }
24
+
25
+ Card.propTypes = {
26
+ image: PropTypes.string.isRequired,
27
+ imageAlt: PropTypes.string.isRequired,
28
+ text: PropTypes.string.isRequired,
29
+ link: PropTypes.string.isRequired,
30
+ name: PropTypes.string.isRequired,
31
+ clickAction: PropTypes.func,
32
+ };
app/modules/onboarding/assets/js/components/card.scss ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-onboarding {
2
+
3
+ &__card {
4
+ border: 1px solid $platform-darker-gray;
5
+ border-radius: 8px;
6
+ padding: 40px 40px;
7
+ background-color: $white;
8
+ cursor: pointer;
9
+ flex-grow: 1;
10
+ display: flex;
11
+ flex-direction: column;
12
+ align-items: center;
13
+
14
+ &-image,
15
+ &-text {
16
+ width: 345px;
17
+ }
18
+
19
+ &-image {
20
+ margin-bottom: 44px;
21
+ }
22
+
23
+ &-text {
24
+ font-size: 20px;
25
+ font-weight: 700;
26
+ text-align: center;
27
+ color: $platform-darkest-gray;
28
+ }
29
+
30
+ &:hover {
31
+ box-shadow: 4px 4px 0 0 $black;
32
+ }
33
+
34
+ &:active {
35
+ box-shadow: initial;
36
+ }
37
+ }
38
+ }
app/modules/onboarding/assets/js/components/checkbox-with-label.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Checkbox from '../../../../../assets/js/ui/atoms/checkbox';
2
+
3
+ export default function CheckBoxWithLabel( props ) {
4
+ return (
5
+ // eslint-disable-next-line jsx-a11y/label-has-for
6
+ <label className="e-onboarding__checkbox-label">
7
+ <Checkbox
8
+ className="e-onboarding__checkbox-input"
9
+ checked={ props.checked }
10
+ onChange={ ( event ) => props.onChangeCallback( event ) }
11
+ />
12
+ { props.labelText }
13
+ </label>
14
+ );
15
+ }
16
+
17
+ CheckBoxWithLabel.propTypes = {
18
+ checked: PropTypes.any,
19
+ labelText: PropTypes.string,
20
+ onChangeCallback: PropTypes.func.isRequired,
21
+ };
app/modules/onboarding/assets/js/components/checklist-item.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function ChecklistItem( props ) {
2
+ return (
3
+ <li className="e-onboarding__checklist-item">
4
+ <i className="eicon-check-circle" />
5
+ { props.children }
6
+ </li>
7
+ );
8
+ }
9
+
10
+ ChecklistItem.propTypes = {
11
+ children: PropTypes.string,
12
+ };
app/modules/onboarding/assets/js/components/checklist.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function Checklist( props ) {
2
+ return (
3
+ <ul className="e-onboarding__checklist">
4
+ { props.children }
5
+ </ul>
6
+ );
7
+ }
8
+
9
+ Checklist.propTypes = {
10
+ children: PropTypes.any.isRequired,
11
+ };
app/modules/onboarding/assets/js/components/checklist.scss ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-onboarding__checklist {
2
+ padding-inline-start: 0;
3
+
4
+ &-item {
5
+ display: flex;
6
+ align-items: center;
7
+ font-size: 12px;
8
+ margin-bottom: 12px;
9
+
10
+ .eicon-check-circle {
11
+ margin-inline-end: 9px;
12
+ font-size: 16px;
13
+ font-weight: 600;
14
+ }
15
+ }
16
+ }
app/modules/onboarding/assets/js/components/go-pro-popover.js ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { OnboardingContext } from '../context/context';
2
+
3
+ import PopoverDialog from 'elementor-app/ui/popover-dialog/popover-dialog';
4
+ import Checklist from './checklist';
5
+ import ChecklistItem from './checklist-item';
6
+ import Button from './button';
7
+ import { useCallback, useContext } from 'react';
8
+
9
+ export default function GoProPopover( props ) {
10
+ const { state, updateState } = useContext( OnboardingContext );
11
+
12
+ // Handle the Pro Upload popup window.
13
+ const alreadyHaveProButtonRef = useCallback( ( alreadyHaveProButton ) => {
14
+ if ( ! alreadyHaveProButton ) {
15
+ return;
16
+ }
17
+
18
+ alreadyHaveProButton.addEventListener( 'click', ( event ) => {
19
+ event.preventDefault();
20
+
21
+ elementorCommon.events.dispatchEvent( {
22
+ event: 'already have pro',
23
+ version: '',
24
+ details: {
25
+ placement: elementorAppConfig.onboarding.eventPlacement,
26
+ step: state.currentStep,
27
+ },
28
+ } );
29
+
30
+ // Open the Pro Upload screen in a popup.
31
+ window.open(
32
+ alreadyHaveProButton.href + '&mode=popup',
33
+ 'elementorUploadPro',
34
+ `toolbar=no, menubar=no, width=728, height=531, top=100, left=100`,
35
+ );
36
+
37
+ // Run the callback for when the upload succeeds.
38
+ elementorCommon.elements.$body
39
+ .on( 'elementor/upload-and-install-pro/success', () => {
40
+ updateState( {
41
+ hasPro: true,
42
+ proNotice: {
43
+ type: 'success',
44
+ icon: 'eicon-check-circle-o',
45
+ message: __( 'Elementor Pro has been successfully installed.', 'elementor' ),
46
+ },
47
+ } );
48
+ } );
49
+ } );
50
+ }, [] );
51
+
52
+ // The buttonsConfig prop is an array of objects. To find the 'Go Pro' button, we need to iterate over the object.
53
+ const goProButton = props.buttonsConfig.find( ( button ) => 'go-pro' === button.id ),
54
+ getElProButton = {
55
+ text: __( 'Upgrade Now', 'elementor' ),
56
+ className: 'e-onboarding__go-pro-cta',
57
+ target: '_blank',
58
+ href: 'https://elementor.com/pro/?utm_source=onboarding-wizard&utm_campaign=gopro&utm_medium=wp-dash&utm_content=top-bar-dropdown&utm_term=' + elementorAppConfig.onboarding.onboardingVersion,
59
+ tabIndex: 0,
60
+ onClick: () => {
61
+ elementorCommon.events.dispatchEvent( {
62
+ event: 'get elementor pro',
63
+ version: '',
64
+ details: {
65
+ placement: elementorAppConfig.onboarding.eventPlacement,
66
+ step: state.currentStep,
67
+ },
68
+ } );
69
+ },
70
+ };
71
+
72
+ return (
73
+ <PopoverDialog
74
+ targetRef={ goProButton.elRef }
75
+ wrapperClass="e-onboarding__go-pro"
76
+ >
77
+ <div className="e-onboarding__go-pro-content">
78
+ <h2 className="e-onboarding__go-pro-title">{ __( 'Ready to Go Pro?', 'elementor' ) }</h2>
79
+ <Checklist>
80
+ <ChecklistItem>{ __( '90+ Basic & Pro widgets', 'elementor' ) }</ChecklistItem>
81
+ <ChecklistItem>{ __( '300+ Basic & Pro templates', 'elementor' ) }</ChecklistItem>
82
+ <ChecklistItem>{ __( 'Premium Support', 'elementor' ) }</ChecklistItem>
83
+ </Checklist>
84
+ <div className="e-onboarding__go-pro-paragraph">
85
+ { __( 'And so much more!', 'elementor' ) }
86
+ </div>
87
+ <div className="e-onboarding__go-pro-paragraph">
88
+ <Button buttonSettings={ getElProButton } />
89
+ </div>
90
+ <div className="e-onboarding__go-pro-paragraph">
91
+ <a tabIndex="0" className="e-onboarding__go-pro-already-have" ref={ alreadyHaveProButtonRef } href={ elementorAppConfig.onboarding.urls.uploadPro } rel="opener">
92
+ { __( 'Already have Elementor Pro?', 'elementor' ) }
93
+ </a>
94
+ </div>
95
+ </div>
96
+ </PopoverDialog>
97
+ );
98
+ }
99
+
100
+ GoProPopover.propTypes = {
101
+ buttonsConfig: PropTypes.array.isRequired,
102
+ };
app/modules/onboarding/assets/js/components/layout/footer-buttons.js ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Grid from 'elementor-app/ui/grid/grid';
2
+ import Button from '../button';
3
+ import SkipButton from '../skip-button';
4
+
5
+ export default function FooterButtons( { actionButton, skipButton, className } ) {
6
+ let classNames = 'e-onboarding__footer';
7
+
8
+ if ( className ) {
9
+ classNames += ' ' + className;
10
+ }
11
+
12
+ return (
13
+ <Grid container alignItems="center" justify="space-between" className={ classNames }>
14
+ { actionButton && <Button buttonSettings={ actionButton } type="action" /> }
15
+ { skipButton && <SkipButton button={ skipButton } /> }
16
+ </Grid>
17
+ );
18
+ }
19
+
20
+ FooterButtons.propTypes = {
21
+ actionButton: PropTypes.object,
22
+ skipButton: PropTypes.object,
23
+ className: PropTypes.string,
24
+ };
app/modules/onboarding/assets/js/components/layout/header.js ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+ import { OnboardingContext } from '../../context/context';
3
+ import Grid from 'elementor-app/ui/grid/grid';
4
+ import GoProPopover from '../go-pro-popover';
5
+ import HeaderButtons from 'elementor-app/layout/header-buttons';
6
+ import usePageTitle from 'elementor-app/hooks/use-page-title';
7
+
8
+ export default function Header( props ) {
9
+ usePageTitle( { title: props.title } );
10
+
11
+ const { state } = useContext( OnboardingContext );
12
+
13
+ const onClose = () => {
14
+ elementorCommon.events.dispatchEvent( {
15
+ event: 'close modal',
16
+ version: '',
17
+ details: {
18
+ placement: elementorAppConfig.onboarding.eventPlacement,
19
+ step: state.currentStep,
20
+ },
21
+ } );
22
+
23
+ window.top.location = elementorAppConfig.admin_url;
24
+ };
25
+
26
+ return (
27
+ <Grid container alignItems="center" justify="space-between" className="eps-app__header e-onboarding__header">
28
+ <div className="eps-app__logo-title-wrapper e-onboarding__header-logo">
29
+ <i className="eps-app__logo eicon-elementor" />
30
+ <img
31
+ src={ elementorCommon.config.urls.assets + 'images/logo-platform.svg' }
32
+ alt={ __( 'Elementor Logo', 'elementor' ) }
33
+ />
34
+ </div>
35
+ <HeaderButtons buttons={ props.buttons } onClose={ onClose } />
36
+ { ! state.hasPro && <GoProPopover buttonsConfig={ props.buttons } /> }
37
+ </Grid>
38
+ );
39
+ }
40
+
41
+ Header.propTypes = {
42
+ title: PropTypes.string,
43
+ buttons: PropTypes.arrayOf( PropTypes.object ),
44
+ };
45
+
46
+ Header.defaultProps = {
47
+ buttons: [],
48
+ };
app/modules/onboarding/assets/js/components/layout/header.scss ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-onboarding__header {
2
+
3
+ &-logo {
4
+
5
+ .eps-app__logo {
6
+ background-color: $platform-darkest-gray;
7
+ color: $white;
8
+ width: 1.3rem;
9
+ height: 1.3rem;
10
+ line-height: 1.3rem;
11
+ font-size: 0.48rem;
12
+
13
+ &:not(:last-child) {
14
+ margin-inline-end: 7px;
15
+ }
16
+ }
17
+
18
+ img {
19
+ width: 104px;
20
+ }
21
+ }
22
+
23
+ .eps-app__header-btn {
24
+ display: flex;
25
+ align-items: center;
26
+ font-size: 13px;
27
+
28
+ .eps-icon:not(:last-child) {
29
+ margin-inline-end: 7px;
30
+ }
31
+ }
32
+
33
+ .eps-button {
34
+ color: $platform-darkest-gray;
35
+
36
+ &__go-pro-btn {
37
+ background-color: $platform-accent;
38
+ color: $white;
39
+ padding: 4px 8px;
40
+ border-radius: 4px;
41
+ font-weight: 700;
42
+ font-size: 12px;
43
+ transition: 0.5s;
44
+
45
+ &:hover {
46
+ background: lighten($platform-accent, 10%)
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ .e-onboarding__go-pro {
53
+ width: 288px;
54
+ font-size: 12px;
55
+
56
+ &-title {
57
+ font-size: 18px;
58
+ font-weight: 700;
59
+ color: $platform-accent;
60
+ }
61
+
62
+ &-cta {
63
+ display: inline-block;
64
+ color: $platform-accent;
65
+ padding: 9px;
66
+ border: 1px solid $platform-accent;
67
+
68
+ // Specificity.
69
+ &.e-onboarding__button {
70
+ font-size: 14px;
71
+ }
72
+ }
73
+
74
+ &-paragraph {
75
+
76
+ &:not(:last-child) {
77
+ margin-bottom: 20px;
78
+ }
79
+ }
80
+
81
+ &-already-have {
82
+ text-decoration: underline;
83
+ }
84
+ }
app/modules/onboarding/assets/js/components/layout/layout.js ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useRef, useContext, useEffect } from 'react';
2
+ import { OnboardingContext } from '../../context/context';
3
+
4
+ import Header from './header';
5
+ import ProgressBar from '../progress-bar/progress-bar';
6
+ import Content from '../../../../../../assets/js/layout/content';
7
+ import Connect from '../../utils/connect';
8
+
9
+ export default function Layout( props ) {
10
+ useEffect( () => {
11
+ // Send modal load event for current step.
12
+ elementorCommon.events.dispatchEvent( {
13
+ event: 'modal load',
14
+ version: '',
15
+ details: {
16
+ placement: elementorAppConfig.onboarding.eventPlacement,
17
+ step: props.pageId,
18
+ user_state: elementorCommon.config.library_connect.is_connected ? 'logged' : 'anon',
19
+ },
20
+ } );
21
+
22
+ updateState( {
23
+ currentStep: props.pageId,
24
+ nextStep: props.nextStep || '',
25
+ proNotice: null,
26
+ } );
27
+ }, [ props.pageId ] );
28
+
29
+ const { state, updateState } = useContext( OnboardingContext ),
30
+ headerButtons = [],
31
+ goProButtonRef = useRef(),
32
+ createAccountButton = {
33
+ id: 'create-account',
34
+ text: __( 'Create Account', 'elementor-pro' ),
35
+ hideText: false,
36
+ elRef: useRef(),
37
+ url: elementorAppConfig.onboarding.urls.signUp + elementorAppConfig.onboarding.utms.connectTopBar,
38
+ target: '_blank',
39
+ rel: 'opener',
40
+ onClick: () => {
41
+ elementorCommon.events.dispatchEvent( {
42
+ event: 'create account',
43
+ version: '',
44
+ details: {
45
+ placement: elementorAppConfig.onboarding.eventPlacement,
46
+ step: state.currentStep,
47
+ source: 'header',
48
+ },
49
+ } );
50
+ },
51
+ };
52
+
53
+ if ( state.isLibraryConnected ) {
54
+ headerButtons.push( {
55
+ id: 'my-elementor',
56
+ text: __( 'My Elementor', 'elementor-pro' ),
57
+ hideText: false,
58
+ icon: 'eicon-user-circle-o',
59
+ url: 'https://my.elementor.com/?utm_source=onboarding-wizard&utm_medium=wp-dash&utm_campaign=my-account&utm_content=top-bar&utm_term=' + elementorAppConfig.onboarding.onboardingVersion,
60
+ target: '_blank',
61
+ onClick: () => {
62
+ elementorCommon.events.dispatchEvent( {
63
+ event: 'my elementor click',
64
+ version: '',
65
+ details: {
66
+ placement: elementorAppConfig.onboarding.eventPlacement,
67
+ step: state.currentStep,
68
+ source: 'header',
69
+ },
70
+ } );
71
+ },
72
+ } );
73
+ } else {
74
+ headerButtons.push( createAccountButton );
75
+ }
76
+
77
+ if ( ! state.hasPro ) {
78
+ headerButtons.push( {
79
+ id: 'go-pro',
80
+ text: __( 'Upgrade', 'elementor' ),
81
+ hideText: false,
82
+ className: 'eps-button__go-pro-btn',
83
+ url: 'https://elementor.com/pro/?utm_source=onboarding-wizard&utm_campaign=gopro&utm_medium=wp-dash&utm_content=top-bar&utm_term=' + elementorAppConfig.onboarding.onboardingVersion,
84
+ target: '_blank',
85
+ elRef: goProButtonRef,
86
+ onClick: () => {
87
+ elementorCommon.events.dispatchEvent( {
88
+ event: 'go pro',
89
+ version: '',
90
+ details: {
91
+ placement: elementorAppConfig.onboarding.eventPlacement,
92
+ step: state.currentStep,
93
+ },
94
+ } );
95
+ },
96
+ } );
97
+ }
98
+
99
+ return (
100
+ <div className="eps-app__lightbox">
101
+ <div className="eps-app e-onboarding">
102
+ { ! state.isLibraryConnected &&
103
+ <Connect
104
+ buttonRef={ createAccountButton.elRef }
105
+ />
106
+ }
107
+ <Header
108
+ title={ __( 'Getting Started', 'elementor' ) }
109
+ buttons={ headerButtons }
110
+ />
111
+ <div className={ 'eps-app__main e-onboarding__page-' + props.pageId }>
112
+ <Content className="e-onboarding__content">
113
+ <ProgressBar />
114
+ { props.children }
115
+ </Content>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ );
120
+ }
121
+
122
+ Layout.propTypes = {
123
+ pageId: PropTypes.string.isRequired,
124
+ nextStep: PropTypes.string,
125
+ className: PropTypes.string,
126
+ children: PropTypes.any.isRequired,
127
+ };
app/modules/onboarding/assets/js/components/layout/layout.scss ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-onboarding {
2
+
3
+ &__page-content {
4
+ margin-bottom: 70px;
5
+
6
+ &-start {
7
+ max-width: 675px;
8
+ text-align: start;
9
+ flex-basis: 555px;
10
+ align-self: start;
11
+ }
12
+
13
+ &-end {
14
+ min-width: 440px;
15
+ max-width: 540px;
16
+
17
+ img {
18
+ width: 100%;
19
+ }
20
+ }
21
+
22
+ &-section-title {
23
+ font-family: 'DM Serif Display', serif;
24
+ font-size: 36px;
25
+ font-weight: 700;
26
+ color: $platform-darkest-gray;
27
+ }
28
+
29
+ &-section-text {
30
+ font-size: 18px;
31
+ color: #3A3F45;
32
+ }
33
+ }
34
+ }
app/modules/onboarding/assets/js/components/layout/page-content-layout.js ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+ import { OnboardingContext } from '../../context/context';
3
+ import Grid from 'elementor-app/ui/grid/grid';
4
+ import Notice from '../notice';
5
+ import FooterButtons from './footer-buttons';
6
+
7
+ export default function PageContentLayout( props ) {
8
+ const { state } = useContext( OnboardingContext );
9
+
10
+ const printNotices = () => {
11
+ return (
12
+ <>
13
+ { props.noticeState && <Notice noticeState={ props.noticeState } /> }
14
+ { state.proNotice && <Notice noticeState={ state.proNotice } /> }
15
+ </>
16
+ );
17
+ };
18
+
19
+ return (
20
+ <>
21
+ <Grid container alignItems="center" justify="space-between" className="e-onboarding__page-content">
22
+ <div className="e-onboarding__page-content-start">
23
+ <h1 className="e-onboarding__page-content-section-title">
24
+ { props.title }
25
+ </h1>
26
+ <div className="e-onboarding__page-content-section-text">
27
+ { props.children }
28
+ </div>
29
+ </div>
30
+ <div className="e-onboarding__page-content-end">
31
+ <img src={ props.image } alt="Information" />
32
+ </div>
33
+ </Grid>
34
+ <div className="e-onboarding__notice-container">
35
+ { props.noticeState || state.proNotice
36
+ ? printNotices()
37
+ : <div className="e-onboarding__notice-empty-spacer" /> }
38
+ </div>
39
+ <FooterButtons actionButton={ props.actionButton } skipButton={ props.skipButton } />
40
+ </>
41
+ );
42
+ }
43
+
44
+ PageContentLayout.propTypes = {
45
+ title: PropTypes.string,
46
+ children: PropTypes.any,
47
+ image: PropTypes.string,
48
+ actionButton: PropTypes.object,
49
+ skipButton: PropTypes.object,
50
+ noticeState: PropTypes.any,
51
+ };
app/modules/onboarding/assets/js/components/new-page-kit-list-item.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Card, CardHeader, CardBody, Heading, CardImage, CardOverlay, Grid, Button } from '@elementor/app-ui';
2
+
3
+ import '../../../../kit-library/assets/js/components/kit-list-item.scss';
4
+
5
+ const NewPageKitListItem = () => {
6
+ return (
7
+ <Card className="e-onboarding__kit-library-card e-kit-library__kit-item">
8
+ <CardHeader>
9
+ <Heading
10
+ tag="h3"
11
+ title={ __( 'Blank Canvas', 'elementor' ) }
12
+ variant="h5"
13
+ className="eps-card__headline"
14
+ >
15
+ { __( 'Blank Canvas', 'elementor' ) }
16
+ </Heading>
17
+ </CardHeader>
18
+ <CardBody>
19
+ <CardImage alt={ __( 'Blank Canvas', 'elementor' ) } src={ elementorCommon.config.urls.assets + 'images/app/onboarding/Blank_Preview.jpg' || '' }>
20
+ <CardOverlay>
21
+ <Grid container direction="column" className="e-kit-library__kit-item-overlay">
22
+ <Button
23
+ className="e-kit-library__kit-item-overlay-overview-button"
24
+ text={ __( 'Create New Elementor Page', 'elementor' ) }
25
+ icon="eicon-single-page"
26
+ url={ elementorAppConfig.onboarding.urls.createNewPage }
27
+ />
28
+ </Grid>
29
+ </CardOverlay>
30
+ </CardImage>
31
+ </CardBody>
32
+ </Card>
33
+ );
34
+ };
35
+
36
+ export default React.memo( NewPageKitListItem );
app/modules/onboarding/assets/js/components/notice.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function Notice( props ) {
2
+ return (
3
+ <div className={ `e-onboarding__notice e-onboarding__notice--${ props.noticeState.type }` }>
4
+ <i className={ props.noticeState.icon } />
5
+ <span className="e-onboarding__notice-text">{ props.noticeState.message }</span>
6
+ </div>
7
+ );
8
+ }
9
+
10
+ Notice.propTypes = {
11
+ noticeState: PropTypes.object,
12
+ };
app/modules/onboarding/assets/js/components/notice.scss ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-onboarding__notice {
2
+ display: inline-block;
3
+ padding: 12px 14px;
4
+ margin-bottom: 16px;
5
+ color: $platform-darker-gray;
6
+
7
+ &--error {
8
+ background-color: $platform-danger-bg;
9
+
10
+ i {
11
+ font-size: 16px;
12
+ color: $platform-danger-text;
13
+ }
14
+ }
15
+
16
+ &--success {
17
+ background-color: $platform-success-bg;
18
+
19
+ i {
20
+ color: $platform-success-text;
21
+ }
22
+ }
23
+
24
+ i {
25
+ margin-inline-end: 14px;
26
+ }
27
+
28
+ &-empty-spacer {
29
+ height: 61px;
30
+ }
31
+ }
app/modules/onboarding/assets/js/components/progress-bar/progress-bar-item.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+ import { OnboardingContext } from '../../context/context';
3
+
4
+ export default function ProgressBarItem( props ) {
5
+ const { state } = useContext( OnboardingContext ),
6
+ stepCompleted = 'completed' === state.steps[ props.id ],
7
+ stepSkipped = 'skipped' === state.steps[ props.id ];
8
+
9
+ let itemClasses = 'e-onboarding__progress-bar-item';
10
+
11
+ if ( props.id === state.currentStep ) {
12
+ itemClasses += ' e-onboarding__progress-bar-item--active';
13
+ } else if ( stepCompleted ) {
14
+ itemClasses += ' e-onboarding__progress-bar-item--completed';
15
+ } else if ( stepSkipped ) {
16
+ itemClasses += ' e-onboarding__progress-bar-item--skipped';
17
+ }
18
+
19
+ return (
20
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
21
+ <div onClick={ props.onClick } className={ itemClasses }>
22
+ <div className="e-onboarding__progress-bar-item-icon">
23
+ { stepCompleted ? <i className="eicon-check" /> : props.index + 1 }
24
+ </div>
25
+ { props.title }
26
+ </div>
27
+ );
28
+ }
29
+
30
+ ProgressBarItem.propTypes = {
31
+ index: PropTypes.number.isRequired,
32
+ id: PropTypes.string.isRequired,
33
+ title: PropTypes.string.isRequired,
34
+ route: PropTypes.string,
35
+ onClick: PropTypes.func,
36
+ };
app/modules/onboarding/assets/js/components/progress-bar/progress-bar.js ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+ import { OnboardingContext } from '../../context/context';
3
+ import { useNavigate } from '@reach/router';
4
+
5
+ import ProgressBarItem from './progress-bar-item';
6
+
7
+ export default function ProgressBar() {
8
+ const { state } = useContext( OnboardingContext ),
9
+ navigate = useNavigate(),
10
+ progressBarItemsConfig = [
11
+ {
12
+ id: 'account',
13
+ title: __( 'Elementor Account', 'elementor' ),
14
+ route: 'account',
15
+ },
16
+ ];
17
+
18
+ // If hello theme is already activated when onboarding starts, don't show this step in the onboarding.
19
+ if ( ! elementorAppConfig.onboarding.helloActivated ) {
20
+ progressBarItemsConfig.push( {
21
+ id: 'hello',
22
+ title: __( 'Hello Theme', 'elementor' ),
23
+ route: 'hello',
24
+ } );
25
+ }
26
+
27
+ progressBarItemsConfig.push( {
28
+ id: 'siteName',
29
+ title: __( 'Site Name', 'elementor' ),
30
+ route: 'site-name',
31
+ },
32
+ {
33
+ id: 'siteLogo',
34
+ title: __( 'Site Logo', 'elementor' ),
35
+ route: 'site-logo',
36
+ },
37
+ {
38
+ id: 'goodToGo',
39
+ title: __( 'Good to Go', 'elementor' ),
40
+ route: 'good-to-go',
41
+ } );
42
+
43
+ const progressBarItems = progressBarItemsConfig.map( ( itemConfig, index ) => {
44
+ itemConfig.index = index;
45
+
46
+ if ( state.steps[ itemConfig.id ] ) {
47
+ itemConfig.onClick = () => {
48
+ elementorCommon.events.dispatchEvent( {
49
+ event: 'step click',
50
+ version: '',
51
+ details: {
52
+ placement: elementorAppConfig.onboarding.eventPlacement,
53
+ step: state.currentStep,
54
+ next_step: itemConfig.id,
55
+ },
56
+ } );
57
+
58
+ navigate( '/onboarding/' + itemConfig.id );
59
+ };
60
+ }
61
+
62
+ return <ProgressBarItem key={ itemConfig.id } { ...itemConfig } />;
63
+ } );
64
+
65
+ return (
66
+ <div className="e-onboarding__progress-bar">
67
+ { progressBarItems }
68
+ </div>
69
+ );
70
+ }
app/modules/onboarding/assets/js/components/progress-bar/progress-bar.scss ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-onboarding__progress-bar {
2
+ display: flex;
3
+ justify-content: center;
4
+ margin-bottom: 125px;
5
+
6
+ &-item {
7
+ display: flex;
8
+ align-items: center;
9
+ color: $platform-mid-gray;
10
+ font-size: 0.75rem;
11
+
12
+ &-icon {
13
+ display: inline-block;
14
+ font-family: 'DM Serif Display', serif;
15
+ text-align: center;
16
+ width: 1.1rem;
17
+ height: 1.1rem;
18
+ line-height: 1rem;
19
+ font-size: 0.75rem;
20
+ font-weight: bold;
21
+ border-radius: 50%;
22
+ border: 1px solid $platform-mid-gray;
23
+ margin-inline-end: 8px;
24
+ flex-shrink: 0;
25
+ }
26
+
27
+ &:not(:last-child) {
28
+ margin-inline-end: 22px;
29
+
30
+ &:after {
31
+ font-family: 'eicons';
32
+ margin-inline-start: 22px;
33
+ content: '\e89e'; // eicon-angle-right
34
+ }
35
+ }
36
+
37
+ &--active {
38
+ color: $platform-primary;
39
+ }
40
+
41
+ &--active,
42
+ &--completed {
43
+
44
+ .e-onboarding__progress-bar-item-icon {
45
+ color: $white;
46
+ border-color: $platform-primary;
47
+ background-color: $platform-primary;
48
+ }
49
+ }
50
+
51
+ &--skipped,
52
+ &--completed {
53
+ cursor: pointer;
54
+ }
55
+
56
+ &--skipped {
57
+
58
+ .e-onboarding__progress-bar-item-icon {
59
+ color: $white;
60
+ background-color: $platform-mid-gray;
61
+ }
62
+
63
+ }
64
+ }
65
+ }
app/modules/onboarding/assets/js/components/skip-button.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+ import { OnboardingContext } from '../context/context';
3
+ import { useNavigate } from '@reach/router';
4
+
5
+ import Button from './button';
6
+
7
+ export default function SkipButton( props ) {
8
+ const { button, className } = props,
9
+ { state, updateState } = useContext( OnboardingContext ),
10
+ navigate = useNavigate(),
11
+ skipStep = () => {
12
+ const mutatedState = JSON.parse( JSON.stringify( state ) );
13
+
14
+ mutatedState.steps[ state.currentStep ] = 'skipped';
15
+
16
+ updateState( mutatedState );
17
+
18
+ if ( state.nextStep ) {
19
+ navigate( 'onboarding/' + state.nextStep );
20
+ }
21
+ },
22
+ action = button.action || skipStep;
23
+
24
+ // Make sure the 'action' prop doesn't get printed on the button markup which causes an error.
25
+ delete button.action;
26
+
27
+ // If the button is a link, no onClick functionality should be added.
28
+ button.onClick = () => {
29
+ elementorCommon.events.dispatchEvent( {
30
+ event: 'skip',
31
+ version: '',
32
+ details: {
33
+ placement: elementorAppConfig.onboarding.eventPlacement,
34
+ step: state.currentStep,
35
+ },
36
+ } );
37
+
38
+ if ( ! button.href ) {
39
+ action();
40
+ }
41
+ };
42
+
43
+ return <Button buttonSettings={ button } className={ className } type="skip" />;
44
+ }
45
+
46
+ SkipButton.propTypes = {
47
+ button: PropTypes.object.isRequired,
48
+ className: PropTypes.string,
49
+ };
app/modules/onboarding/assets/js/context/context.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createContext, useCallback, useState } from 'react';
2
+
3
+ export const OnboardingContext = createContext( {} );
4
+
5
+ export function ContextProvider( props ) {
6
+ const onboardingConfig = elementorAppConfig.onboarding,
7
+ initialState = {
8
+ // eslint-disable-next-line camelcase
9
+ hasPro: elementorAppConfig.hasPro,
10
+ isLibraryConnected: onboardingConfig.isLibraryConnected,
11
+ isHelloThemeInstalled: onboardingConfig.helloInstalled,
12
+ isHelloThemeActivated: onboardingConfig.helloActivated,
13
+ siteName: onboardingConfig.siteName,
14
+ siteLogo: onboardingConfig.siteLogo,
15
+ proNotice: '',
16
+ currentStep: '',
17
+ nextStep: '',
18
+ steps: {
19
+ account: false,
20
+ hello: false,
21
+ siteName: false,
22
+ siteLogo: false,
23
+ goodToGo: false,
24
+ },
25
+ },
26
+ [ state, setState ] = useState( initialState ),
27
+ updateState = useCallback( ( newState ) => {
28
+ setState( ( prev ) => ( { ...prev, ...newState } ) );
29
+ }, [ setState ] ),
30
+ getStateObjectToUpdate = ( stateObject, mainChangedPropertyKey, subChangedPropertyKey, subChangedPropertyValue ) => {
31
+ const mutatedStateCopy = JSON.parse( JSON.stringify( stateObject ) );
32
+
33
+ mutatedStateCopy[ mainChangedPropertyKey ][ subChangedPropertyKey ] = subChangedPropertyValue;
34
+
35
+ return mutatedStateCopy;
36
+ };
37
+
38
+ return (
39
+ <OnboardingContext.Provider value={ { state, setState, updateState, getStateObjectToUpdate } }>
40
+ { props.children }
41
+ </OnboardingContext.Provider>
42
+ );
43
+ }
44
+
45
+ ContextProvider.propTypes = {
46
+ children: PropTypes.any,
47
+ };
app/modules/onboarding/assets/js/module.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ import router from '@elementor/router';
2
+
3
+ export default class Onboarding {
4
+ constructor() {
5
+ router.addRoute( {
6
+ path: '/onboarding/*',
7
+ component: React.lazy( () => import( /* webpackChunkName: 'onboarding' */ './app' ) ),
8
+ } );
9
+ }
10
+ }
app/modules/onboarding/assets/js/pages/account.js ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useRef, useContext, useState } from 'react';
2
+ import { useNavigate } from '@reach/router';
3
+ import { OnboardingContext } from '../context/context';
4
+ import Connect from '../utils/connect';
5
+ import Layout from '../components/layout/layout';
6
+ import PageContentLayout from '../components/layout/page-content-layout';
7
+
8
+ export default function Account() {
9
+ const { state, updateState, getStateObjectToUpdate } = useContext( OnboardingContext ),
10
+ [ noticeState, setNoticeState ] = useState( null ),
11
+ navigate = useNavigate(),
12
+ pageId = 'account',
13
+ nextStep = state.isHelloThemeActivated ? 'siteName' : 'hello',
14
+ actionButtonRef = useRef(),
15
+ alreadyHaveAccountLinkRef = useRef();
16
+
17
+ let skipButton;
18
+
19
+ if ( 'completed' !== state.steps[ pageId ] ) {
20
+ skipButton = {
21
+ text: __( 'Skip', 'elementor' ),
22
+ };
23
+ }
24
+
25
+ let pageTexts = {};
26
+
27
+ if ( state.isLibraryConnected ) {
28
+ pageTexts = {
29
+ firstLine: __( 'To get the most out of Elementor, we\'ll help you take your first steps:', 'elementor' ),
30
+ listItems: [
31
+ __( 'Set your site\'s theme', 'elementor' ),
32
+ __( 'Give your site a name & logo', 'elementor' ),
33
+ __( 'Choose how to start creating', 'elementor' ),
34
+ ],
35
+ };
36
+ } else {
37
+ pageTexts = {
38
+ firstLine: __( 'To get the most out of Elementor, we’ll connect your account.', 'elementor' ) +
39
+ ' ' + __( 'Then you can:', 'elementor' ),
40
+ listItems: [
41
+ __( 'Choose from countless professional templates', 'elementor' ),
42
+ __( 'Manage your site with our handy dashboard', 'elementor' ),
43
+ __( 'Take part in the community forum, share & grow together', 'elementor' ),
44
+ ],
45
+ };
46
+ }
47
+
48
+ // If the user is not connected, the on-click action is handled by the <Connect> component, so there is no onclick
49
+ // property.
50
+ const actionButton = {
51
+ role: 'button',
52
+ };
53
+
54
+ if ( state.isLibraryConnected ) {
55
+ actionButton.text = __( 'Let’s do it', 'elementor' );
56
+
57
+ actionButton.onClick = () => {
58
+ elementorCommon.events.dispatchEvent( {
59
+ event: 'next',
60
+ version: '',
61
+ details: {
62
+ placement: elementorAppConfig.onboarding.eventPlacement,
63
+ step: state.currentStep,
64
+ },
65
+ } );
66
+
67
+ updateState( getStateObjectToUpdate( state, 'steps', pageId, 'completed' ) );
68
+
69
+ navigate( 'onboarding/' + nextStep );
70
+ };
71
+ } else {
72
+ actionButton.text = __( 'Create my account', 'elementor' );
73
+ actionButton.href = elementorAppConfig.onboarding.urls.signUp + elementorAppConfig.onboarding.utms.connectCta;
74
+ actionButton.ref = actionButtonRef;
75
+ actionButton.onClick = () => {
76
+ elementorCommon.events.dispatchEvent( {
77
+ event: 'create account',
78
+ version: '',
79
+ details: {
80
+ placement: elementorAppConfig.onboarding.eventPlacement,
81
+ source: 'cta',
82
+ },
83
+ } );
84
+ };
85
+ }
86
+
87
+ const connectSuccessCallback = ( data ) => {
88
+ const stateToUpdate = getStateObjectToUpdate( state, 'steps', pageId, 'completed' );
89
+
90
+ stateToUpdate.isLibraryConnected = true;
91
+
92
+ elementorCommon.config.library_connect.is_connected = true;
93
+ elementorCommon.config.library_connect.current_access_level = data.kits_access_level || data.access_level || 0;
94
+
95
+ updateState( stateToUpdate );
96
+
97
+ elementorCommon.events.dispatchEvent( {
98
+ event: 'indication prompt',
99
+ version: '',
100
+ details: {
101
+ placement: elementorAppConfig.onboarding.eventPlacement,
102
+ step: state.currentStep,
103
+ action_state: 'success',
104
+ action: 'connect account',
105
+ },
106
+ } );
107
+
108
+ setNoticeState( {
109
+ type: 'success',
110
+ icon: 'eicon-check-circle-o',
111
+ message: 'Alrighty - your account is connected.',
112
+ } );
113
+
114
+ navigate( 'onboarding/' + nextStep );
115
+ };
116
+
117
+ const connectFailureCallback = () => {
118
+ elementorCommon.events.dispatchEvent( {
119
+ event: 'indication prompt',
120
+ version: '',
121
+ details: {
122
+ placement: elementorAppConfig.onboarding.eventPlacement,
123
+ step: state.currentStep,
124
+ action_state: 'failure',
125
+ action: 'connect account',
126
+ },
127
+ } );
128
+
129
+ setNoticeState( {
130
+ type: 'error',
131
+ icon: 'eicon-warning',
132
+ message: __( 'Oops, the connection failed. Try again.', 'elementor' ),
133
+ } );
134
+
135
+ navigate( 'onboarding/' + nextStep );
136
+ };
137
+
138
+ return (
139
+ <Layout pageId={ pageId } nextStep={ nextStep }>
140
+ <PageContentLayout
141
+ image={ elementorCommon.config.urls.assets + 'images/app/onboarding/Illustration_Account.svg' }
142
+ title={ __( 'You\'re here! Let\'s set things up.', 'elementor' ) }
143
+ actionButton={ actionButton }
144
+ skipButton={ skipButton }
145
+ noticeState={ noticeState }
146
+ >
147
+ { actionButton.ref && ! state.isLibraryConnected &&
148
+ <Connect
149
+ buttonRef={ actionButton.ref }
150
+ successCallback={ ( data ) => connectSuccessCallback( data ) }
151
+ errorCallback={ connectFailureCallback }
152
+ /> }
153
+ <span>
154
+ { pageTexts.firstLine }
155
+ </span>
156
+ <ul>
157
+ { pageTexts.listItems.map( ( listItem, index ) => {
158
+ return <li key={ 'listItem' + index }>{ listItem }</li>;
159
+ } ) }
160
+ </ul>
161
+ </PageContentLayout>
162
+ {
163
+ ! state.isLibraryConnected && (
164
+ <div className="e-onboarding__footnote">
165
+ <p>
166
+ { __( 'Already have one?', 'elementor' ) + ' ' }
167
+ <a
168
+ ref={ alreadyHaveAccountLinkRef }
169
+ href={ elementorAppConfig.onboarding.urls.connect + elementorAppConfig.onboarding.utms.connectCtaLink }
170
+ onClick={ () => {
171
+ elementorCommon.events.dispatchEvent( {
172
+ event: 'connect account',
173
+ version: '',
174
+ details: {
175
+ placement: elementorAppConfig.onboarding.eventPlacement,
176
+ },
177
+ } );
178
+ } }
179
+ >
180
+ { __( 'Connect your account', 'elementor' ) }
181
+ </a>
182
+ </p>
183
+ <Connect
184
+ buttonRef={ alreadyHaveAccountLinkRef }
185
+ successCallback={ connectSuccessCallback }
186
+ errorCallback={ connectFailureCallback }
187
+ />
188
+ </div>
189
+ )
190
+ }
191
+ </Layout>
192
+ );
193
+ }
app/modules/onboarding/assets/js/pages/account.scss ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ .e-onboarding__page-account {
2
+
3
+ .e-onboarding__checkbox-label {
4
+ margin-top: 50px;
5
+ }
6
+ }
app/modules/onboarding/assets/js/pages/good-to-go.js ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Grid from 'elementor-app/ui/grid/grid';
2
+ import Layout from '../components/layout/layout';
3
+ import Card from '../components/card';
4
+ import FooterButtons from '../components/layout/footer-buttons';
5
+
6
+ export default function GoodToGo() {
7
+ const pageId = 'goodToGo',
8
+ skipButton = {
9
+ text: __( 'Skip', 'elementor' ),
10
+ href: elementorAppConfig.onboarding.urls.createNewPage,
11
+ },
12
+ kitLibraryLink = elementorAppConfig.onboarding.urls.kitLibrary + '&referrer=onboarding';
13
+
14
+ return (
15
+ <Layout pageId={ pageId }>
16
+ <h1 className="e-onboarding__page-content-section-title">
17
+ { __( 'That\'s a wrap! What\'s next?', 'elementor' ) }
18
+ </h1>
19
+ <div className="e-onboarding__page-content-section-text">
20
+ { __( 'There are two ways to get started with Elementor:', 'elementor' ) }
21
+ </div>
22
+ <Grid container alignItems="center" justify="space-between" className="e-onboarding__cards-grid e-onboarding__page-content">
23
+ <Card
24
+ name="blank"
25
+ image={ elementorCommon.config.urls.assets + 'images/app/onboarding/Blank_Canvas.svg' }
26
+ imageAlt={ __( 'Click here to create a new page and open it in Elementor Editor', 'elementor' ) }
27
+ text={ __( 'Edit a blank canvas with the Elementor Editor', 'elementor' ) }
28
+ link={ elementorAppConfig.onboarding.urls.createNewPage }
29
+ />
30
+ <Card
31
+ name="template"
32
+ image={ elementorCommon.config.urls.assets + 'images/app/onboarding/Library.svg' }
33
+ imageAlt={ __( 'Click here to go to Elementor\'s Kit Library', 'elementor' ) }
34
+ text={ __( 'Browse from +100 templates or import your own', 'elementor' ) }
35
+ link={ kitLibraryLink }
36
+ clickAction={ () => {
37
+ // The location is reloaded to make sure the Kit Library's state is re-created.
38
+ location.href = kitLibraryLink;
39
+ location.reload();
40
+ } }
41
+ />
42
+ </Grid>
43
+ <FooterButtons skipButton={ skipButton } className="e-onboarding__good-to-go-footer" />
44
+ </Layout>
45
+ );
46
+ }
app/modules/onboarding/assets/js/pages/good-to-go.scss ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-onboarding {
2
+
3
+ &__page-goodToGo {
4
+
5
+ .e-onboarding__page-content-section-title,
6
+ .e-onboarding__page-content-section-text {
7
+ text-align: center;
8
+ }
9
+ }
10
+
11
+ &__cards-grid {
12
+
13
+ // Specificity.
14
+ &.e-onboarding__page-content {
15
+ margin-top: 48px;
16
+ margin-bottom: 48px;
17
+ }
18
+
19
+ .e-onboarding__card {
20
+ max-width: 555px;
21
+
22
+ &:not(:last-child) {
23
+ margin-inline-end: 20px;
24
+ }
25
+ }
26
+ }
27
+
28
+ &__good-to-go-footer {
29
+ justify-content: end;
30
+ }
31
+ }
app/modules/onboarding/assets/js/pages/hello-theme.js ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable @wordpress/i18n-ellipsis */
2
+ import { useContext, useEffect, useState, useCallback } from 'react';
3
+ import { OnboardingContext } from '../context/context';
4
+ import { useNavigate } from '@reach/router';
5
+ import useAjax from 'elementor-app/hooks/use-ajax';
6
+ import Layout from '../components/layout/layout';
7
+ import PageContentLayout from '../components/layout/page-content-layout';
8
+
9
+ export default function HelloTheme() {
10
+ const { state, updateState, getStateObjectToUpdate } = useContext( OnboardingContext ),
11
+ { ajaxState: activateHelloThemeAjaxState, setAjax: setActivateHelloThemeAjaxState } = useAjax(),
12
+ // Allow navigating back to this screen if it was completed in the onboarding.
13
+ [ helloInstalledInOnboarding, setHelloInstalledInOnboarding ] = useState( false ),
14
+ [ isInstalling, setIsInstalling ] = useState( false ),
15
+ noticeStateSuccess = {
16
+ type: 'success',
17
+ icon: 'eicon-check-circle-o',
18
+ message: __( 'Your site’s got Hello theme. High-five!', 'elementor' ),
19
+ },
20
+ [ noticeState, setNoticeState ] = useState( state.isHelloThemeActivated ? noticeStateSuccess : null ),
21
+ [ activeTimeouts, setActiveTimeouts ] = useState( [] ),
22
+ continueWithHelloThemeText = state.isHelloThemeActivated ? __( 'Next', 'elementor' ) : __( 'Continue with Hello Theme', 'elementor' ),
23
+ [ actionButtonText, setActionButtonText ] = useState( continueWithHelloThemeText ),
24
+ navigate = useNavigate(),
25
+ pageId = 'hello',
26
+ nextStep = 'siteName',
27
+ goToNextScreen = () => navigate( 'onboarding/' + nextStep );
28
+
29
+ /**
30
+ * Setup
31
+ *
32
+ * If Hello Theme is already activated when onboarding starts, This screen is unneeded and is marked as 'completed'
33
+ * and skipped.
34
+ */
35
+ useEffect( () => {
36
+ if ( ! helloInstalledInOnboarding && state.isHelloThemeActivated ) {
37
+ const stateToUpdate = getStateObjectToUpdate( state, 'steps', pageId, 'completed' );
38
+
39
+ updateState( stateToUpdate );
40
+
41
+ goToNextScreen();
42
+ }
43
+ }, [] );
44
+
45
+ const resetScreenContent = () => {
46
+ // Clear any active timeouts for changing the action button text during installation.
47
+ activeTimeouts.forEach( ( timeoutID ) => clearTimeout( timeoutID ) );
48
+
49
+ setActiveTimeouts( [] );
50
+
51
+ setIsInstalling( false );
52
+
53
+ setActionButtonText( continueWithHelloThemeText );
54
+ };
55
+
56
+ /**
57
+ * Callbacks
58
+ */
59
+ const onHelloThemeActivationSuccess = useCallback( () => {
60
+ setIsInstalling( false );
61
+
62
+ elementorCommon.events.dispatchEvent( {
63
+ event: 'indication prompt',
64
+ version: '',
65
+ details: {
66
+ placement: elementorAppConfig.onboarding.eventPlacement,
67
+ step: state.currentStep,
68
+ action_state: 'success',
69
+ action: 'hello theme activation',
70
+ },
71
+ } );
72
+
73
+ setNoticeState( noticeStateSuccess );
74
+
75
+ setActionButtonText( __( 'Next', 'elementor' ) );
76
+
77
+ const stateToUpdate = getStateObjectToUpdate( state, 'steps', pageId, 'completed' );
78
+
79
+ stateToUpdate.isHelloThemeActivated = true;
80
+
81
+ updateState( stateToUpdate );
82
+
83
+ setHelloInstalledInOnboarding( true );
84
+
85
+ goToNextScreen();
86
+ }, [] );
87
+
88
+ const onErrorInstallHelloTheme = () => {
89
+ elementorCommon.events.dispatchEvent( {
90
+ event: 'indication prompt',
91
+ version: '',
92
+ details: {
93
+ placement: elementorAppConfig.onboarding.eventPlacement,
94
+ step: state.currentStep,
95
+ action_state: 'failure',
96
+ action: 'hello theme install',
97
+ },
98
+ } );
99
+
100
+ setNoticeState( {
101
+ type: 'error',
102
+ icon: 'eicon-warning',
103
+ message: __( 'There was a problem installing Hello Theme.', 'elementor' ),
104
+ } );
105
+
106
+ resetScreenContent();
107
+ };
108
+
109
+ const activateHelloTheme = () => {
110
+ setIsInstalling( true );
111
+
112
+ updateState( { isHelloThemeInstalled: true } );
113
+
114
+ setActivateHelloThemeAjaxState( {
115
+ data: { action: 'elementor_activate_hello_theme' },
116
+ } );
117
+ };
118
+
119
+ const installHelloTheme = () => {
120
+ if ( ! isInstalling ) {
121
+ setIsInstalling( true );
122
+ }
123
+
124
+ wp.updates.ajax( 'install-theme', {
125
+ slug: 'hello-elementor',
126
+ success: () => activateHelloTheme(),
127
+ error: () => onErrorInstallHelloTheme(),
128
+ } );
129
+ };
130
+
131
+ const sendNextButtonEvent = () => {
132
+ elementorCommon.events.dispatchEvent( {
133
+ event: 'next',
134
+ version: '',
135
+ details: {
136
+ placement: elementorAppConfig.onboarding.eventPlacement,
137
+ step: state.currentStep,
138
+ },
139
+ } );
140
+ };
141
+
142
+ /**
143
+ * Action Button
144
+ */
145
+ const actionButton = {
146
+ text: actionButtonText,
147
+ role: 'button',
148
+ };
149
+
150
+ if ( isInstalling ) {
151
+ actionButton.className = 'e-onboarding__button--processing';
152
+ }
153
+
154
+ if ( state.isHelloThemeActivated ) {
155
+ actionButton.onClick = () => {
156
+ sendNextButtonEvent();
157
+
158
+ goToNextScreen();
159
+ };
160
+ } else {
161
+ actionButton.onClick = () => {
162
+ sendNextButtonEvent();
163
+
164
+ if ( state.isHelloThemeInstalled && ! state.isHelloThemeActivated ) {
165
+ activateHelloTheme();
166
+ } else if ( ! state.isHelloThemeInstalled ) {
167
+ installHelloTheme();
168
+ } else {
169
+ goToNextScreen();
170
+ }
171
+ };
172
+ }
173
+
174
+ /**
175
+ * Skip Button
176
+ */
177
+ let skipButton;
178
+
179
+ if ( 'completed' !== state.steps[ pageId ] ) {
180
+ skipButton = {
181
+ text: __( 'Skip', 'elementor' ),
182
+ };
183
+ }
184
+
185
+ /**
186
+ * Set timeouts for updating the 'Next' button text if the Hello Theme installation is taking too long.
187
+ */
188
+ useEffect( () => {
189
+ if ( isInstalling ) {
190
+ setActionButtonText( (
191
+ <>
192
+ <i className="eicon-loading eicon-animation-spin" aria-hidden="true" />
193
+ </>
194
+ ) );
195
+ }
196
+
197
+ const actionTextTimeouts = [];
198
+
199
+ const timeout4 = setTimeout( () => {
200
+ if ( ! isInstalling ) {
201
+ return;
202
+ }
203
+
204
+ setActionButtonText( (
205
+ <>
206
+ <i className="eicon-loading eicon-animation-spin" aria-hidden="true" />
207
+ <span className="e-onboarding__action-button-text">{ __( 'Hold on, this can take a minute...', 'elementor' ) }</span>
208
+ </>
209
+ ) );
210
+ }, 4000 );
211
+
212
+ actionTextTimeouts.push( timeout4 );
213
+
214
+ const timeout30 = setTimeout( () => {
215
+ if ( ! isInstalling ) {
216
+ return;
217
+ }
218
+
219
+ setActionButtonText( (
220
+ <>
221
+ <i className="eicon-loading eicon-animation-spin" aria-hidden="true" />
222
+ <span className="e-onboarding__action-button-text">{ __( 'Okay, now we\'re really close...', 'elementor' ) }</span>
223
+ </>
224
+ ) );
225
+ }, 30000 );
226
+
227
+ actionTextTimeouts.push( timeout30 );
228
+
229
+ setActiveTimeouts( actionTextTimeouts );
230
+ }, [ isInstalling ] );
231
+
232
+ useEffect( () => {
233
+ if ( 'initial' !== activateHelloThemeAjaxState.status ) {
234
+ if ( 'success' === activateHelloThemeAjaxState.status && activateHelloThemeAjaxState.response?.helloThemeActivated ) {
235
+ onHelloThemeActivationSuccess();
236
+ } else if ( 'error' === activateHelloThemeAjaxState.status ) {
237
+ elementorCommon.events.dispatchEvent( {
238
+ event: 'indication prompt',
239
+ version: '',
240
+ details: {
241
+ placement: elementorAppConfig.onboarding.eventPlacement,
242
+ step: state.currentStep,
243
+ action_state: 'failure',
244
+ action: 'hello theme activation',
245
+ },
246
+ } );
247
+
248
+ setNoticeState( {
249
+ type: 'error',
250
+ icon: 'eicon-warning',
251
+ message: __( 'There was a problem activating Hello Theme.', 'elementor' ),
252
+ } );
253
+
254
+ // Clear any active timeouts for changing the action button text during installation.
255
+ resetScreenContent();
256
+ }
257
+ }
258
+ }, [ activateHelloThemeAjaxState.status ] );
259
+
260
+ return (
261
+ <Layout pageId={ pageId } nextStep={ nextStep }>
262
+ <PageContentLayout
263
+ image={ elementorCommon.config.urls.assets + 'images/app/onboarding/Illustration_Hello.svg' }
264
+ title={ __( 'Every site starts with a theme.', 'elementor' ) }
265
+ actionButton={ actionButton }
266
+ skipButton={ skipButton }
267
+ noticeState={ noticeState }
268
+ >
269
+ <p>
270
+ { __( 'Hello is Elementor\'s official blank canvas theme optimized to build your website exactly the way you want.', 'elementor' ) }
271
+ </p>
272
+ <p>
273
+ { __( 'Here\'s why:', 'elementor' ) }
274
+ </p>
275
+ <ul className="e-onboarding__feature-list">
276
+ <li>{ __( 'Light-weight and fast loading', 'elementor' ) }</li>
277
+ <li>{ __( 'Great for SEO', 'elementor' ) }</li>
278
+ <li>{ __( 'Already being used by 1M+ web creators', 'elementor' ) }</li>
279
+ </ul>
280
+ </PageContentLayout>
281
+ <div className="e-onboarding__footnote">
282
+ { '* ' + __( 'You can switch your theme later on', 'elementor' ) }
283
+ </div>
284
+ </Layout>
285
+ );
286
+ }
app/modules/onboarding/assets/js/pages/hello-theme.scss ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ .e-onboarding {
2
+
3
+ &__action-button-text {
4
+ margin-inline-start: 10px;
5
+ }
6
+ }
app/modules/onboarding/assets/js/pages/site-logo.js ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable jsx-a11y/no-static-element-interactions */
2
+ /* eslint-disable jsx-a11y/click-events-have-key-events */
3
+ import { useContext, useEffect, useState, useCallback } from 'react';
4
+ import { OnboardingContext } from '../context/context';
5
+ import { useNavigate } from '@reach/router';
6
+ import useAjax from 'elementor-app/hooks/use-ajax';
7
+ import DropZone from 'elementor-app/organisms/drop-zone';
8
+ import UnfilteredFilesDialog from 'elementor-app/organisms/unfiltered-files-dialog';
9
+
10
+ import Layout from '../components/layout/layout';
11
+ import PageContentLayout from '../components/layout/page-content-layout';
12
+
13
+ export default function SiteLogo() {
14
+ const { state, updateState, getStateObjectToUpdate } = useContext( OnboardingContext ),
15
+ [ file, setFile ] = useState( state.siteLogo.id ? state.siteLogo : null ),
16
+ [ isUploading, setIsUploading ] = useState( false ),
17
+ [ showUnfilteredFilesDialog, setShowUnfilteredFilesDialog ] = useState( false ),
18
+ [ fileSource, setFileSource ] = useState(),
19
+ [ noticeState, setNoticeState ] = useState( null ),
20
+ { ajaxState: updateLogoAjaxState, setAjax: setUpdateLogoAjax } = useAjax(),
21
+ { ajaxState: uploadImageAjaxState, setAjax: setUploadImageAjax } = useAjax(),
22
+ pageId = 'siteLogo',
23
+ nextStep = 'goodToGo',
24
+ navigate = useNavigate(),
25
+ actionButton = {
26
+ role: 'button',
27
+ onClick: () => {
28
+ elementorCommon.events.dispatchEvent( {
29
+ event: 'next',
30
+ version: '',
31
+ details: {
32
+ placement: elementorAppConfig.onboarding.eventPlacement,
33
+ step: state.currentStep,
34
+ },
35
+ } );
36
+
37
+ if ( file.id ) {
38
+ if ( file.id !== state.siteLogo.id ) {
39
+ updateSiteLogo();
40
+ } else {
41
+ // If the currently displayed logo is already set as the site logo, just go to the next screen.
42
+ const stateToUpdate = getStateObjectToUpdate( state, 'steps', pageId, 'completed' );
43
+
44
+ updateState( stateToUpdate );
45
+
46
+ navigate( 'onboarding/' + nextStep );
47
+ }
48
+ }
49
+ },
50
+ };
51
+
52
+ let skipButton;
53
+
54
+ if ( 'completed' !== state.steps[ pageId ] ) {
55
+ skipButton = {
56
+ text: __( 'Skip', 'elementor' ),
57
+ };
58
+ }
59
+
60
+ if ( isUploading ) {
61
+ actionButton.text = (
62
+ <>
63
+ <i className="eicon-loading eicon-animation-spin" aria-hidden="true" />
64
+ </>
65
+ );
66
+ } else {
67
+ actionButton.text = __( 'Next', 'elementor' );
68
+ }
69
+
70
+ if ( ! file ) {
71
+ actionButton.className = 'e-onboarding__button--disabled';
72
+ }
73
+
74
+ const updateSiteLogo = useCallback( () => {
75
+ setIsUploading( true );
76
+
77
+ setUpdateLogoAjax( {
78
+ data: {
79
+ action: 'elementor_update_site_logo',
80
+ data: JSON.stringify( {
81
+ attachmentId: file.id,
82
+ } ),
83
+ },
84
+ } );
85
+ }, [ file ] );
86
+
87
+ const uploadSiteLogo = ( fileToUpload ) => {
88
+ setIsUploading( true );
89
+
90
+ setUploadImageAjax( {
91
+ data: {
92
+ action: 'elementor_upload_site_logo',
93
+ fileToUpload,
94
+ },
95
+ } );
96
+ };
97
+
98
+ const dismissUnfilteredFilesCallback = () => {
99
+ setIsUploading( false );
100
+
101
+ setFile( null );
102
+
103
+ setShowUnfilteredFilesDialog( false );
104
+ };
105
+
106
+ const onFileSelect = ( selectedFile ) => {
107
+ setFileSource( 'drop' );
108
+
109
+ if ( 'image/svg+xml' === selectedFile.type && ! elementorAppConfig.onboarding.isUnfilteredFilesEnabled ) {
110
+ setFile( selectedFile );
111
+
112
+ setIsUploading( true );
113
+
114
+ setShowUnfilteredFilesDialog( true );
115
+ } else {
116
+ setFile( selectedFile );
117
+
118
+ setNoticeState( null );
119
+
120
+ uploadSiteLogo( selectedFile );
121
+ }
122
+ };
123
+
124
+ const onImageRemoveClick = () => {
125
+ elementorCommon.events.dispatchEvent( {
126
+ event: 'remove selected logo',
127
+ version: '',
128
+ details: {
129
+ placement: elementorAppConfig.onboarding.eventPlacement,
130
+ },
131
+ } );
132
+
133
+ setFile( null );
134
+ };
135
+
136
+ /**
137
+ * Ajax Callbacks
138
+ */
139
+ // Run the callback for the new image upload AJAX request.
140
+ useEffect( () => {
141
+ if ( 'initial' !== uploadImageAjaxState.status ) {
142
+ if ( 'success' === uploadImageAjaxState.status && uploadImageAjaxState.response?.imageAttachment?.id ) {
143
+ elementorCommon.events.dispatchEvent( {
144
+ event: 'logo image uploaded',
145
+ version: '',
146
+ details: {
147
+ placement: elementorAppConfig.onboarding.eventPlacement,
148
+ source: fileSource,
149
+ },
150
+ } );
151
+
152
+ setIsUploading( false );
153
+
154
+ setFile( uploadImageAjaxState.response.imageAttachment );
155
+
156
+ if ( noticeState ) {
157
+ setNoticeState( null );
158
+ }
159
+ } else if ( 'error' === uploadImageAjaxState.status ) {
160
+ setIsUploading( false );
161
+
162
+ setFile( null );
163
+
164
+ elementorCommon.events.dispatchEvent( {
165
+ event: 'indication prompt',
166
+ version: '',
167
+ details: {
168
+ placement: elementorAppConfig.onboarding.eventPlacement,
169
+ action_state: 'failure',
170
+ action: 'logo image upload',
171
+ },
172
+ } );
173
+
174
+ setNoticeState( {
175
+ type: 'error',
176
+ icon: 'eicon-warning',
177
+ message: 'That didn\'t work. Try uploading your file again.',
178
+ } );
179
+ }
180
+ }
181
+ }, [ uploadImageAjaxState.status ] );
182
+
183
+ // Run the callback for the site logo update AJAX request.
184
+ useEffect( () => {
185
+ if ( 'initial' !== updateLogoAjaxState.status ) {
186
+ if ( 'success' === updateLogoAjaxState.status && updateLogoAjaxState.response?.siteLogoUpdated ) {
187
+ elementorCommon.events.dispatchEvent( {
188
+ event: 'logo image updated',
189
+ version: '',
190
+ details: {
191
+ placement: elementorAppConfig.onboarding.eventPlacement,
192
+ source: fileSource,
193
+ },
194
+ } );
195
+
196
+ setIsUploading( false );
197
+
198
+ if ( noticeState ) {
199
+ setNoticeState( null );
200
+ }
201
+
202
+ const stateToUpdate = getStateObjectToUpdate( state, 'steps', pageId, 'completed' );
203
+
204
+ stateToUpdate.siteLogo = {
205
+ id: file.id,
206
+ url: file.url,
207
+ };
208
+
209
+ updateState( stateToUpdate );
210
+
211
+ navigate( 'onboarding/' + nextStep );
212
+ } else if ( 'error' === updateLogoAjaxState.status ) {
213
+ setIsUploading( false );
214
+
215
+ elementorCommon.events.dispatchEvent( {
216
+ event: 'indication prompt',
217
+ version: '',
218
+ details: {
219
+ placement: elementorAppConfig.onboarding.eventPlacement,
220
+ step: state.currentStep,
221
+ action_state: 'failure',
222
+ action: 'update site logo',
223
+ },
224
+ } );
225
+
226
+ setNoticeState( {
227
+ type: 'error',
228
+ icon: 'eicon-warning',
229
+ message: 'That didn\'t work. Try uploading your file again.',
230
+ } );
231
+ }
232
+ }
233
+ }, [ updateLogoAjaxState.status ] );
234
+
235
+ return (
236
+ <Layout pageId={ pageId } nextStep={ nextStep }>
237
+ <PageContentLayout
238
+ image={ elementorCommon.config.urls.assets + 'images/app/onboarding/Illustration_Setup.svg' }
239
+ title={ __( 'Have a logo? Add it here.', 'elementor' ) }
240
+ actionButton={ actionButton }
241
+ skipButton={ skipButton }
242
+ noticeState={ noticeState }
243
+ >
244
+ <span>
245
+ { __( 'Otherwise, you can skip this and add one later.', 'elementor' ) }
246
+ </span>
247
+ { ( file && ! showUnfilteredFilesDialog )
248
+ ? (
249
+ <div className={ 'e-onboarding__logo-container' + ( isUploading ? ' e-onboarding__is-uploading' : '' ) }>
250
+ <div className="e-onboarding__logo-remove" onClick={ () => onImageRemoveClick() }>
251
+ <i className="eicon-trash-o" />
252
+ </div>
253
+ <img src={ file.url } alt={ __( 'Potential Site Logo', 'elementor' ) } />
254
+ </div>
255
+ )
256
+ : <>
257
+ <DropZone
258
+ className="e-onboarding__drop-zone"
259
+ heading={ __( 'Drop image here', 'elementor' ) }
260
+ secondaryText={ __( 'or', 'elementor' ) }
261
+ buttonText={ __( 'Open Media Library', 'elementor' ) }
262
+ buttonVariant="outlined"
263
+ buttonColor="cta"
264
+ icon={ '' }
265
+ type="wp-media"
266
+ filetypes={ [ 'jpg', 'jpeg', 'png', 'svg' ] }
267
+ onFileSelect={ ( selectedFile ) => onFileSelect( selectedFile ) }
268
+ onWpMediaSelect={ ( frame ) => {
269
+ // Get media attachment details from the frame state
270
+ var attachment = frame.state().get( 'selection' ).first().toJSON();
271
+
272
+ setFileSource( 'browse' );
273
+ setFile( attachment );
274
+
275
+ setNoticeState( null );
276
+ } }
277
+ onButtonClick={ () => {
278
+ elementorCommon.events.dispatchEvent( {
279
+ event: 'browse file click',
280
+ version: '',
281
+ details: {
282
+ placement: elementorAppConfig.onboarding.eventPlacement,
283
+ step: state.currentStep,
284
+ },
285
+ } );
286
+ } }
287
+ // TODO: DEAL WITH ERROR
288
+ onError={ ( error ) => {
289
+ if ( 'file_not_allowed' === error.id ) {
290
+ elementorCommon.events.dispatchEvent( {
291
+ event: 'indication prompt',
292
+ version: '',
293
+ details: {
294
+ placement: elementorAppConfig.onboarding.eventPlacement,
295
+ step: state.currentStep,
296
+ action_state: 'failure',
297
+ action: 'logo upload format',
298
+ },
299
+ } );
300
+
301
+ setNoticeState( {
302
+ type: 'error',
303
+ icon: 'eicon-warning',
304
+ message: __( 'This file type is not supported. Try a different type of file', 'elementor' ),
305
+ } );
306
+ }
307
+ } }
308
+ />
309
+ </>
310
+ }
311
+ {
312
+ <UnfilteredFilesDialog
313
+ show={ showUnfilteredFilesDialog }
314
+ setShow={ setShowUnfilteredFilesDialog }
315
+ confirmModalText={ __( 'This allows Elementor to scan your SVGs for malicious content. If you do not wish to allow this, use a different image format.', 'elementor' ) }
316
+ errorModalText={ __( 'There was a problem with enabling SVG uploads. Try again, or use another image format.', 'elementor' ) }
317
+ onReady={ () => {
318
+ setShowUnfilteredFilesDialog( false );
319
+
320
+ elementorAppConfig.onboarding.isUnfilteredFilesEnabled = true;
321
+
322
+ uploadSiteLogo( file );
323
+ } }
324
+ onDismiss={ () => dismissUnfilteredFilesCallback() }
325
+ onCancel={ () => dismissUnfilteredFilesCallback() }
326
+ />
327
+ }
328
+ </PageContentLayout>
329
+ </Layout>
330
+ );
331
+ }
app/modules/onboarding/assets/js/pages/site-logo.scss ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-onboarding {
2
+
3
+ &__page-siteLogo {
4
+
5
+ .e-app-upload-file__button {
6
+ color: $platform-primary;
7
+ border-color: $platform-primary;
8
+ }
9
+ }
10
+
11
+ &__drop-zone {
12
+ margin-top: 3rem;
13
+ }
14
+
15
+ .eps-display-3 {
16
+ font-size: 16px;
17
+ margin-bottom: 0.5rem;
18
+ }
19
+
20
+ .e-app-upload-file__button {
21
+ max-width: 198px;
22
+ margin: 0 auto;
23
+ }
24
+
25
+ .e-app-drop-zone__secondary-text {
26
+ font-size: 14px;
27
+ }
28
+
29
+ &__logo {
30
+
31
+ &-container {
32
+ position: relative;
33
+ margin-top: 3rem;
34
+ height: 220px;
35
+ width: fit-content;
36
+ display: flex;
37
+ align-items: center;
38
+ min-width: 220px;
39
+
40
+ img {
41
+ height: auto;
42
+ max-height: 100%;
43
+ width: auto;
44
+ }
45
+ }
46
+
47
+ &-remove {
48
+ position: absolute;
49
+ right: 8px;
50
+ top: 5px;
51
+
52
+ i {
53
+ font-size: 16px;
54
+ width: 15px;
55
+ }
56
+
57
+ &:hover {
58
+ cursor: pointer;
59
+ }
60
+ }
61
+ }
62
+
63
+ &__is-uploading {
64
+ visibility: hidden;
65
+ }
66
+ }
app/modules/onboarding/assets/js/pages/site-name.js ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext, useEffect, useRef, useState } from 'react';
2
+ import { OnboardingContext } from '../context/context';
3
+ import { useNavigate } from '@reach/router';
4
+ import useAjax from 'elementor-app/hooks/use-ajax';
5
+
6
+ import Layout from '../components/layout/layout';
7
+ import PageContentLayout from '../components/layout/page-content-layout';
8
+
9
+ export default function SiteName() {
10
+ const { state, updateState, getStateObjectToUpdate } = useContext( OnboardingContext ),
11
+ { ajaxState, setAjax } = useAjax(),
12
+ [ noticeState, setNoticeState ] = useState( null ),
13
+ [ siteNameInputValue, setSiteNameInputValue ] = useState( state.siteName ),
14
+ pageId = 'siteName',
15
+ nextStep = 'siteLogo',
16
+ navigate = useNavigate(),
17
+ nameInputRef = useRef(),
18
+ actionButton = {
19
+ text: __( 'Next', 'elementor' ),
20
+ onClick: () => {
21
+ elementorCommon.events.dispatchEvent( {
22
+ event: 'next',
23
+ version: '',
24
+ details: {
25
+ placement: elementorAppConfig.onboarding.eventPlacement,
26
+ step: state.currentStep,
27
+ },
28
+ } );
29
+
30
+ // Only run the site name update AJAX if the new name is different than the existing one and it isn't empty.
31
+ if ( nameInputRef.current.value !== state.siteName && '' !== nameInputRef.current.value ) {
32
+ setAjax( {
33
+ data: {
34
+ action: 'elementor_update_site_name',
35
+ data: JSON.stringify( {
36
+ siteName: nameInputRef.current.value,
37
+ } ),
38
+ },
39
+ } );
40
+ } else if ( nameInputRef.current.value === state.siteName ) {
41
+ const stateToUpdate = getStateObjectToUpdate( state, 'steps', pageId, 'completed' );
42
+
43
+ updateState( stateToUpdate );
44
+
45
+ navigate( 'onboarding/' + nextStep );
46
+ } else {
47
+ const stateToUpdate = getStateObjectToUpdate( state, 'steps', pageId, 'skipped' );
48
+
49
+ updateState( stateToUpdate );
50
+
51
+ navigate( 'onboarding/' + nextStep );
52
+ }
53
+ },
54
+ };
55
+
56
+ let skipButton;
57
+
58
+ if ( 'completed' !== state.steps[ pageId ] ) {
59
+ skipButton = {
60
+ text: __( 'Skip', 'elementor' ),
61
+ };
62
+ }
63
+
64
+ if ( ! siteNameInputValue ) {
65
+ actionButton.className = 'e-onboarding__button--disabled';
66
+ }
67
+
68
+ // Run the callback for the site name update AJAX request.
69
+ useEffect( () => {
70
+ if ( 'initial' !== ajaxState.status ) {
71
+ if ( 'success' === ajaxState.status && ajaxState.response?.siteNameUpdated ) {
72
+ const stateToUpdate = getStateObjectToUpdate( state, 'steps', pageId, 'completed' );
73
+
74
+ stateToUpdate.siteName = nameInputRef.current.value;
75
+
76
+ updateState( stateToUpdate );
77
+
78
+ navigate( 'onboarding/' + nextStep );
79
+ } else if ( 'error' === ajaxState.status ) {
80
+ elementorCommon.events.dispatchEvent( {
81
+ event: 'indication prompt',
82
+ version: '',
83
+ details: {
84
+ placement: elementorAppConfig.onboarding.eventPlacement,
85
+ step: state.currentStep,
86
+ action_state: 'failure',
87
+ action: 'site name update',
88
+ },
89
+ } );
90
+
91
+ setNoticeState( {
92
+ type: 'error',
93
+ icon: 'eicon-warning',
94
+ message: __( 'Sorry, the name wasn\'t saved. Try again, or skip for now.', 'elementor' ),
95
+ } );
96
+ }
97
+ }
98
+ }, [ ajaxState.status ] );
99
+
100
+ return (
101
+ <Layout pageId={ pageId } nextStep={ nextStep }>
102
+ <PageContentLayout
103
+ image={ elementorCommon.config.urls.assets + 'images/app/onboarding/Illustration_Setup.svg' }
104
+ title={ __( 'Now, let\'s give your site a name.', 'elementor' ) }
105
+ actionButton={ actionButton }
106
+ skipButton={ skipButton }
107
+ noticeState={ noticeState }
108
+ >
109
+ <p>
110
+ { __( 'This is what your site is called on the WP dashboard, and can be changed later from the general settings - it\'s not your website\'s URL.', 'elementor' ) }
111
+ </p>
112
+ <input
113
+ className="e-onboarding__text-input e-onboarding__site-name-input"
114
+ type="text"
115
+ placeholder="e.g. Eric's Space Shuttles"
116
+ defaultValue={ state.siteName || '' }
117
+ ref={ nameInputRef }
118
+ onChange={ ( event ) => setSiteNameInputValue( event.target.value ) }
119
+ />
120
+ </PageContentLayout>
121
+ </Layout>
122
+ );
123
+ }
app/modules/onboarding/assets/js/pages/site-name.scss ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ .e-onboarding {
2
+
3
+ &__site-name-input {
4
+ margin-top: 80px;
5
+ }
6
+ }
app/modules/onboarding/assets/js/pages/upload-and-install-pro.js ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useCallback, useEffect, useContext, useState } from 'react';
2
+ import useAjax from 'elementor-app/hooks/use-ajax';
3
+ import usePageTitle from 'elementor-app/hooks/use-page-title';
4
+ import Content from '../../../../../assets/js/layout/content';
5
+ import DropZone from '../../../../../assets/js/organisms/drop-zone';
6
+ import Notice from '../components/notice';
7
+ import { OnboardingContext } from '../context/context';
8
+ import ElementorLoading from 'elementor-app/molecules/elementor-loading';
9
+
10
+ export default function UploadAndInstallPro() {
11
+ usePageTitle( { title: __( 'Upload and Install Elementor Pro', 'elementor' ) } );
12
+
13
+ const { state } = useContext( OnboardingContext ),
14
+ { ajaxState: installProZipAjaxState, setAjax: setInstallProZipAjaxState } = useAjax(),
15
+ [ noticeState, setNoticeState ] = useState( null ),
16
+ [ isLoading, setIsLoading ] = useState( false ),
17
+ [ fileSource, setFileSource ] = useState();
18
+
19
+ const uploadProZip = useCallback( ( file ) => {
20
+ setIsLoading( true );
21
+
22
+ setInstallProZipAjaxState( {
23
+ data: {
24
+ action: 'elementor_upload_and_install_pro',
25
+ fileToUpload: file,
26
+ },
27
+ } );
28
+ }, [] );
29
+
30
+ const setErrorNotice = ( error = null, step = 'upload' ) => {
31
+ const errorMessage = error?.message || 'That didn\'t work. Try uploading your file again.';
32
+
33
+ elementorCommon.events.dispatchEvent( {
34
+ event: 'indication prompt',
35
+ version: '',
36
+ details: {
37
+ placement: elementorAppConfig.onboarding.eventPlacement,
38
+ step: state.currentStep,
39
+ action_state: 'failure',
40
+ action: step + ' pro',
41
+ source: fileSource,
42
+ },
43
+ } );
44
+
45
+ setNoticeState( {
46
+ type: 'error',
47
+ icon: 'eicon-warning',
48
+ message: errorMessage,
49
+ } );
50
+ };
51
+
52
+ /**
53
+ * Ajax Callbacks
54
+ */
55
+ // Run the callback that runs when the Pro Upload Ajax returns a response.
56
+ useEffect( () => {
57
+ if ( 'initial' !== installProZipAjaxState.status ) {
58
+ setIsLoading( false );
59
+
60
+ if ( 'success' === installProZipAjaxState.status && installProZipAjaxState.response?.elementorProInstalled ) {
61
+ elementorCommon.events.dispatchEvent( {
62
+ event: 'pro uploaded',
63
+ version: '',
64
+ details: {
65
+ placement: elementorAppConfig.onboarding.eventPlacement,
66
+ step: state.currentStep,
67
+ source: fileSource,
68
+ },
69
+ } );
70
+
71
+ if ( opener && opener !== window ) {
72
+ opener.jQuery( 'body' ).trigger( 'elementor/upload-and-install-pro/success' );
73
+
74
+ window.close();
75
+ opener.focus();
76
+ }
77
+ } else if ( 'error' === installProZipAjaxState.status ) {
78
+ setErrorNotice( 'install' );
79
+ }
80
+ }
81
+ }, [ installProZipAjaxState.status ] );
82
+
83
+ const onProUploadHelpLinkClick = () => {
84
+ elementorCommon.events.dispatchEvent( {
85
+ event: 'pro plugin upload help',
86
+ version: '',
87
+ details: {
88
+ placement: elementorAppConfig.onboarding.eventPlacement,
89
+ step: state.currentStep,
90
+ },
91
+ } );
92
+ };
93
+
94
+ if ( isLoading ) {
95
+ return <ElementorLoading loadingText={ __( 'Uploading', 'elementor' ) } />;
96
+ }
97
+
98
+ return (
99
+ <div className="eps-app e-onboarding__upload-pro">
100
+ <Content>
101
+ <DropZone
102
+ className="e-onboarding__upload-pro-drop-zone"
103
+ onFileSelect={ ( file, event, source ) => {
104
+ setFileSource( source );
105
+ uploadProZip( file );
106
+ } }
107
+ onError={ ( error ) => setErrorNotice( error, 'upload' ) }
108
+ filetypes={ [ 'zip' ] }
109
+ buttonColor="cta"
110
+ buttonVariant="contained"
111
+ heading={ __( 'Import your Elementor Pro plugin file', 'elementor' ) }
112
+ text={ __( 'Drag & Drop your .zip file here', 'elementor' ) }
113
+ secondaryText={ __( 'or', 'elementor' ) }
114
+ buttonText={ __( 'Browse', 'elementor' ) }
115
+ />
116
+ { noticeState && <Notice noticeState={ noticeState } /> }
117
+ <div className="e-onboarding__upload-pro-get-file">
118
+ { __( 'Don\'t know where to get the file from?', 'elementor' ) + ' ' }
119
+ { /* eslint-disable-next-line react/jsx-no-target-blank */ }
120
+ <a onClick={ () => onProUploadHelpLinkClick() } href={ 'https://my.elementor.com/subscriptions/' + elementorAppConfig.onboarding.utms.downloadPro } target="_blank">
121
+ { __( 'Click here', 'elementor' ) }
122
+ </a>
123
+ </div>
124
+ </Content>
125
+ </div>
126
+ );
127
+ }
app/modules/onboarding/assets/js/pages/upload-and-install-pro.scss ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .e-onboarding__upload-pro {
2
+ flex-direction: row;
3
+ justify-content: center;
4
+ font-family: 'DM Sans', 'Roboto', sans-serif;
5
+ text-align: center;
6
+
7
+ .eps-app__content {
8
+ overflow-y: hidden;
9
+ max-width: 1113px;
10
+ padding: 30px;
11
+ }
12
+
13
+ &-drop-zone {
14
+ margin-bottom: 24px;
15
+
16
+ h1 {
17
+ font-family: 'Source Serif Pro', 'Roboto', sans-serif;
18
+ }
19
+
20
+ .e-app-drag-drop {
21
+ padding: 48px;
22
+ }
23
+
24
+ .eps-display-3,
25
+ .e-app-drop-zone__text,
26
+ .e-app-drop-zone__secondary-text {
27
+ margin-bottom: 12px;
28
+ }
29
+
30
+ .e-app-upload-file__button {
31
+ background-color: $platform-primary;
32
+ color: $platform-text;
33
+ padding: 14px 40px;
34
+ border-color: $platform-primary;
35
+ }
36
+ }
37
+
38
+ &-get-file {
39
+ font-size: 12px;
40
+ margin-top: 24px;
41
+
42
+ a {
43
+ text-decoration: underline;
44
+ }
45
+ }
46
+
47
+ .e-onboarding__notice {
48
+ margin-bottom: 0;
49
+ }
50
+ }
app/modules/onboarding/assets/js/utils/connect.js ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useContext } from 'react';
2
+ import { OnboardingContext } from '../context/context';
3
+
4
+ export default function Connect( props ) {
5
+ const { state, updateState, getStateObjectToUpdate } = useContext( OnboardingContext );
6
+
7
+ const connectSuccessCallback = ( data ) => {
8
+ const stateToUpdate = getStateObjectToUpdate( state, 'steps', 'account', 'completed' );
9
+
10
+ elementorCommon.config.library_connect.is_connected = true;
11
+ elementorCommon.config.library_connect.current_access_level = data.kits_access_level || data.access_level || 0;
12
+
13
+ stateToUpdate.isLibraryConnected = true;
14
+
15
+ updateState( stateToUpdate );
16
+ };
17
+
18
+ useEffect( () => {
19
+ jQuery( props.buttonRef.current ).elementorConnect( {
20
+ success: ( data ) => props.successCallback ? props.successCallback( data ) : connectSuccessCallback( data ),
21
+ error: () => {
22
+ if ( props.errorCallback ) {
23
+ props.errorCallback();
24
+ }
25
+ },
26
+ popup: {
27
+ width: 726,
28
+ height: 534,
29
+ },
30
+ } );
31
+ }, [] );
32
+
33
+ return null;
34
+ }
35
+
36
+ Connect.propTypes = {
37
+ buttonRef: PropTypes.object.isRequired,
38
+ successCallback: PropTypes.func,
39
+ errorCallback: PropTypes.func,
40
+ };
app/modules/onboarding/assets/scss/onboarding.scss ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "//fonts.googleapis.com/css2?family=DM%20Sans&display=swap";
2
+ @import "//fonts.googleapis.com/css2?family=Source%20Serif%20Pro&display=swap";
3
+
4
+ .e-onboarding {
5
+ font-family: 'DM Sans', 'Roboto', sans-serif;
6
+
7
+ .eps-app__main {
8
+ justify-content: center;
9
+ // For small screens, move the scrollbar from the padded content container to the top level main container.
10
+ overflow-y: auto;
11
+ }
12
+
13
+ &__content {
14
+ max-width: 1135px;
15
+ padding: initial;
16
+ margin: 2.75rem;
17
+ // For small screens, move the scrollbar from the padded content container to the top level main container.
18
+ overflow-y: initial;
19
+ }
20
+
21
+ &__checkbox {
22
+
23
+ &-label {
24
+ display: flex;
25
+ line-height: 18px;
26
+ margin-bottom: 27px;
27
+ }
28
+
29
+ &-input {
30
+ margin-inline-end: 14px;
31
+ width: 16px;
32
+ height: 16px;
33
+ border-color: $platform-mid-gray;
34
+ border-radius: 2px;
35
+
36
+ &:checked {
37
+ background-color: $platform-dark-gray;
38
+
39
+ &::after {
40
+ margin-bottom: 15%;
41
+ width: 6px;
42
+ height: 9px;
43
+ border-width: 0 2px 2px 0;
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ &__feature-list {
50
+ margin-bottom: 40px;
51
+ }
52
+
53
+ &__text-input {
54
+ font-size: 14px;
55
+ width: 325px;
56
+ padding: 12px 16px;
57
+ color: $platform-mid-gray;
58
+ border: 1px solid $platform-gray-light;
59
+
60
+ &:focus-visible {
61
+ outline: initial;
62
+ border: 1px solid $platform-darker-gray;
63
+ }
64
+
65
+ &::placeholder {
66
+ color: $platform-gray-lighter;
67
+ }
68
+ }
69
+
70
+ &__footnote {
71
+ margin-top: 24px;
72
+ width: 325px;
73
+ text-align: center;
74
+
75
+ a {
76
+ text-decoration: underline;
77
+ color: $platform-darker-gray;
78
+ }
79
+ }
80
+ }
81
+
82
+ #e-app ~ #__wp-uploader-id-2 .media-modal {
83
+ max-width: 1024px;
84
+ max-height: 768px;
85
+ margin: auto;
86
+ }
app/modules/onboarding/module.php ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Elementor\App\Modules\Onboarding;
3
+
4
+ use Automatic_Upgrader_Skin;
5
+ use Elementor\Core\Base\Module as BaseModule;
6
+ use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
7
+ use Elementor\Core\Common\Modules\Connect\Apps\Library;
8
+ use Elementor\Core\Files\Uploads_Manager;
9
+ use Elementor\Plugin;
10
+ use Elementor\Tracker;
11
+ use Plugin_Upgrader;
12
+
13
+ if ( ! defined( 'ABSPATH' ) ) {
14
+ exit; // Exit if accessed directly
15
+ }
16
+
17
+ /**
18
+ * Onboarding Module
19
+ *
20
+ * Responsible for initializing Elementor App functionality
21
+ *
22
+ * @since 3.6.0
23
+ */
24
+ class Module extends BaseModule {
25
+
26
+ const VERSION = '1.0.0';
27
+ const ONBOARDING_OPTION = 'elementor_onboarded';
28
+
29
+ /**
30
+ * Get name.
31
+ *
32
+ * @since 3.6.0
33
+ * @access public
34
+ *
35
+ * @return string
36
+ */
37
+ public function get_name() {
38
+ return 'onboarding';
39
+ }
40
+
41
+ /**
42
+ * Set Onboarding Settings
43
+ *
44
+ * Creates an array of module settings that is localized into the JS App config.
45
+ *
46
+ * @since 3.6.0
47
+ */
48
+ private function set_onboarding_settings() {
49
+ if ( ! Plugin::$instance->common ) {
50
+ return;
51
+ }
52
+
53
+ // Get the published pages and posts
54
+ $pages_and_posts = new \WP_Query( [
55
+ 'post_type' => [ 'page', 'post' ],
56
+ 'post_status' => 'publish',
57
+ 'update_post_meta_cache' => false,
58
+ 'update_post_term_cache' => false,
59
+ 'no_found_rows' => true,
60
+ ] );
61
+
62
+ $custom_site_logo_id = get_theme_mod( 'custom_logo' );
63
+ $custom_logo_src = wp_get_attachment_image_src( $custom_site_logo_id, 'full' );
64
+ $site_name = get_option( 'blogname', '' );
65
+
66
+ $hello_theme = wp_get_theme( 'hello-elementor' );
67
+ $hello_theme_errors = is_object( $hello_theme->errors() ) ? $hello_theme->errors()->errors : [];
68
+
69
+ /** @var Library $library */
70
+ $library = Plugin::$instance->common->get_component( 'connect' )->get_app( 'library' );
71
+
72
+ Plugin::$instance->app->set_settings( 'onboarding', [
73
+ 'eventPlacement' => 'Onboarding wizard',
74
+ 'onboardingAlreadyRan' => get_option( self::ONBOARDING_OPTION ),
75
+ 'onboardingVersion' => self::VERSION,
76
+ 'isLibraryConnected' => $library->is_connected(),
77
+ // Used to check if the Hello Elementor theme is installed but not activated.
78
+ 'helloInstalled' => empty( $hello_theme_errors['theme_not_found'] ),
79
+ 'helloActivated' => 'hello-elementor' === get_option( 'template' ),
80
+ // The "Use Hello theme on my site" checkbox should be checked by default only if this condition is met.
81
+ 'helloOptOut' => count( $pages_and_posts->posts ) < 5,
82
+ 'siteName' => esc_html( $site_name ),
83
+ 'isUnfilteredFilesEnabled' => Uploads_Manager::are_unfiltered_uploads_enabled(),
84
+ 'urls' => [
85
+ 'kitLibrary' => Plugin::$instance->app->get_base_url() . '#/kit-library?order[direction]=desc&order[by]=featuredIndex',
86
+ 'createNewPage' => Plugin::$instance->documents->get_create_new_post_url(),
87
+ 'connect' => $library->get_admin_url( 'authorize', [
88
+ 'utm_source' => 'onboarding-wizard',
89
+ 'utm_campaign' => 'connect-account',
90
+ 'utm_medium' => 'wp-dash',
91
+ 'utm_term' => self::VERSION,
92
+ 'source' => 'generic',
93
+ ] ),
94
+ 'signUp' => $library->get_admin_url( 'authorize', [
95
+ 'utm_source' => 'onboarding-wizard',
96
+ 'utm_campaign' => 'connect-account',
97
+ 'utm_medium' => 'wp-dash',
98
+ 'utm_term' => self::VERSION,
99
+ 'source' => 'generic',
100
+ 'screen_hint' => 'signup',
101
+ ] ),
102
+ 'uploadPro' => Plugin::$instance->app->get_base_url() . '#/onboarding/uploadAndInstallPro?mode=popup',
103
+ ],
104
+ 'siteLogo' => [
105
+ 'id' => $custom_site_logo_id,
106
+ 'url' => $custom_logo_src ? $custom_logo_src[0] : '',
107
+ ],
108
+ 'utms' => [
109
+ 'connectTopBar' => '&utm_content=top-bar',
110
+ 'connectCta' => '&utm_content=cta-button',
111
+ 'connectCtaLink' => '&utm_content=cta-link',
112
+ 'downloadPro' => '?utm_source=onboarding-wizard&utm_campaign=my-account-subscriptions&utm_medium=wp-dash&utm_content=import-pro-plugin&utm_term=' . self::VERSION,
113
+ ],
114
+ 'nonce' => wp_create_nonce( 'onboarding' ),
115
+ ] );
116
+ }
117
+
118
+ /**
119
+ * Get Permission Error Response
120
+ *
121
+ * Returns the response that is returned when the user's capabilities are not sufficient for performing an action.
122
+ *
123
+ * @since 3.6.4
124
+ *
125
+ * @return array
126
+ */
127
+ private function get_permission_error_response() {
128
+ return [
129
+ 'status' => 'error',
130
+ 'payload' => [
131
+ 'error_message' => esc_html__( 'you are not allowed to perform this action', 'elementor' ),
132
+ ],
133
+ ];
134
+ }
135
+
136
+ /**
137
+ * Maybe Update Site Logo
138
+ *
139
+ * If a new name is provided, it will be updated as the Site Name.
140
+ *
141
+ * @since 3.6.0
142
+ *
143
+ * @return array
144
+ */
145
+ private function maybe_update_site_name() {
146
+ $problem_error = [
147
+ 'status' => 'error',
148
+ 'payload' => [
149
+ 'error_message' => esc_html__( 'There was a problem setting your site name', 'elementor' ),
150
+ ],
151
+ ];
152
+
153
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
154
+ if ( empty( $_POST['data'] ) ) {
155
+ return $problem_error;
156
+ }
157
+
158
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
159
+ $data = json_decode( stripslashes( $_POST['data'] ), true );
160
+
161
+ if ( ! isset( $data['siteName'] ) ) {
162
+ return $problem_error;
163
+ }
164
+
165
+ /**
166
+ * Onboarding Site Name
167
+ *
168
+ * Filters the new site name passed by the user to update in Elementor's onboarding process.
169
+ * Elementor runs `esc_html()` on the Site Name passed by the user for security reasons. If a user wants to
170
+ * include special characters in their site name, they can use this filter to override it.
171
+ *
172
+ * @since 3.6.0
173
+ *
174
+ * @param string Escaped new site name
175
+ */
176
+ $new_site_name = apply_filters( 'elementor/onboarding/site-name', $data['siteName'] );
177
+
178
+ // The site name is sanitized in `update_options()`
179
+ update_option( 'blogname', $new_site_name );
180
+
181
+ return [
182
+ 'status' => 'success',
183
+ 'payload' => [
184
+ 'siteNameUpdated' => true,
185
+ ],
186
+ ];
187
+ }
188
+
189
+ /**
190
+ * Maybe Update Site Logo
191
+ *
192
+ * If an image attachment ID is provided, it will be updated as the Site Logo Theme Mod.
193
+ *
194
+ * @since 3.6.0
195
+ *
196
+ * @return array
197
+ */
198
+ private function maybe_update_site_logo() {
199
+ if ( ! current_user_can( 'edit_theme_options' ) ) {
200
+ return $this->get_permission_error_response();
201
+ }
202
+
203
+ $data_error = [
204
+ 'status' => 'error',
205
+ 'payload' => [
206
+ 'error_message' => esc_html__( 'There was a problem setting your site logo', 'elementor' ),
207
+ ],
208
+ ];
209
+
210
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
211
+ if ( empty( $_POST['data'] ) ) {
212
+ return $data_error;
213
+ }
214
+
215
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
216
+ $data = json_decode( stripslashes( $_POST['data'] ), true );
217
+
218
+ // If there is no attachment ID passed or it is not a valid ID, exit here.
219
+ if ( empty( $data['attachmentId'] ) ) {
220
+ return $data_error;
221
+ }
222
+
223
+ $absint_attachment_id = absint( $data['attachmentId'] );
224
+
225
+ if ( 0 === $absint_attachment_id ) {
226
+ return $data_error;
227
+ }
228
+
229
+ $attachment_url = wp_get_attachment_url( $data['attachmentId'] );
230
+
231
+ // Check if the attachment exists. If it does not, exit here.
232
+ if ( ! $attachment_url ) {
233
+ return $data_error;
234
+ }
235
+
236
+ set_theme_mod( 'custom_logo', $absint_attachment_id );
237
+
238
+ return [
239
+ 'status' => 'success',
240
+ 'payload' => [
241
+ 'siteLogoUpdated' => true,
242
+ ],
243
+ ];
244
+ }
245
+
246
+ /**
247
+ * Maybe Upload Logo Image
248
+ *
249
+ * If an image file upload is provided, and it passes validation, it will be uploaded to the site's Media Library.
250
+ *
251
+ * @since 3.6.0
252
+ *
253
+ * @return array
254
+ */
255
+ private function maybe_upload_logo_image() {
256
+ $error_message = esc_html__( 'There was a problem uploading your file', 'elementor' );
257
+
258
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
259
+ if ( empty( $_FILES['fileToUpload'] ) || ! is_array( $_FILES['fileToUpload'] ) ) {
260
+ return [
261
+ 'status' => 'error',
262
+ 'payload' => [
263
+ 'error_message' => $error_message,
264
+ ],
265
+ ];
266
+ }
267
+
268
+ // If the user has allowed it, set the Request's state as an "Elementor Upload" request, in order to add
269
+ // support for non-standard file uploads.
270
+ if ( 'image/svg+xml' === $_FILES['fileToUpload']['type'] ) {
271
+ if ( Uploads_Manager::are_unfiltered_uploads_enabled() ) {
272
+ Plugin::$instance->uploads_manager->set_elementor_upload_state( true );
273
+ } else {
274
+ wp_send_json_error( 'To upload SVG files, you must allow uploading unfiltered files.' );
275
+ }
276
+ }
277
+
278
+ // If the image is an SVG file, sanitation is performed during the import (upload) process.
279
+ $image_attachment = Plugin::$instance->templates_manager->get_import_images_instance()->import( $_FILES['fileToUpload'] );
280
+
281
+ if ( 'image/svg+xml' === $_FILES['fileToUpload']['type'] && Uploads_Manager::are_unfiltered_uploads_enabled() ) {
282
+ // Reset Upload state.
283
+ Plugin::$instance->uploads_manager->set_elementor_upload_state( false );
284
+ }
285
+
286
+ if ( $image_attachment && ! is_wp_error( $image_attachment ) ) {
287
+ $result = [
288
+ 'status' => 'success',
289
+ 'payload' => [
290
+ 'imageAttachment' => $image_attachment,
291
+ ],
292
+ ];
293
+ } else {
294
+ $result = [
295
+ 'status' => 'error',
296
+ 'payload' => [
297
+ 'error_message' => $error_message,
298
+ ],
299
+ ];
300
+ }
301
+
302
+ return $result;
303
+ }
304
+
305
+ /**
306
+ * Activate Hello Theme
307
+ *
308
+ * @since 3.6.0
309
+ *
310
+ * @return array
311
+ */
312
+ private function maybe_activate_hello_theme() {
313
+ if ( ! current_user_can( 'switch_themes' ) ) {
314
+ return $this->get_permission_error_response();
315
+ }
316
+
317
+ switch_theme( 'hello-elementor' );
318
+
319
+ return [
320
+ 'status' => 'success',
321
+ 'payload' => [
322
+ 'helloThemeActivated' => true,
323
+ ],
324
+ ];
325
+ }
326
+
327
+ /**
328
+ * Upload and Install Elementor Pro
329
+ *
330
+ * @since 3.6.0
331
+ *
332
+ * @return array
333
+ */
334
+ private function upload_and_install_pro() {
335
+ if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) {
336
+ return $this->get_permission_error_response();
337
+ }
338
+
339
+ $error_message = esc_html__( 'There was a problem uploading your file', 'elementor' );
340
+
341
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
342
+ if ( empty( $_FILES['fileToUpload'] ) || ! is_array( $_FILES['fileToUpload'] ) ) {
343
+ return [
344
+ 'status' => 'error',
345
+ 'payload' => [
346
+ 'error_message' => $error_message,
347
+ ],
348
+ ];
349
+ }
350
+
351
+ $result = [];
352
+
353
+ if ( ! class_exists( 'Automatic_Upgrader_Skin' ) ) {
354
+ require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
355
+ }
356
+
357
+ $skin = new Automatic_Upgrader_Skin();
358
+ $upgrader = new Plugin_Upgrader( $skin );
359
+ $upload_result = $upgrader->install( $_FILES['fileToUpload']['tmp_name'], [ 'overwrite_package' => false ] );
360
+
361
+ if ( ! $upload_result || is_wp_error( $upload_result ) ) {
362
+ $result = [
363
+ 'status' => 'error',
364
+ 'payload' => [
365
+ 'error_message' => $error_message,
366
+ ],
367
+ ];
368
+ } else {
369
+ $activated = activate_plugin( WP_PLUGIN_DIR . '/elementor-pro/elementor-pro.php', false, false, true );
370
+
371
+ if ( ! is_wp_error( $activated ) ) {
372
+ $result = [
373
+ 'status' => 'success',
374
+ 'payload' => [
375
+ 'elementorProInstalled' => true,
376
+ ],
377
+ ];
378
+ } else {
379
+ $result = [
380
+ 'status' => 'error',
381
+ 'payload' => [
382
+ 'error_message' => $error_message,
383
+ 'elementorProInstalled' => false,
384
+ ],
385
+ ];
386
+ }
387
+ }
388
+
389
+ return $result;
390
+ }
391
+
392
+ private function maybe_update_onboarding_db_option() {
393
+ $db_option = get_option( self::ONBOARDING_OPTION );
394
+
395
+ if ( ! $db_option ) {
396
+ update_option( self::ONBOARDING_OPTION, true );
397
+ }
398
+
399
+ return [
400
+ 'status' => 'success',
401
+ 'payload' => 'onboarding DB',
402
+ ];
403
+ }
404
+
405
+ /**
406
+ * Maybe Handle Ajax
407
+ *
408
+ * This method checks if there are any AJAX actions being
409
+ * @since 3.6.0
410
+ *
411
+ * @return array|null
412
+ */
413
+ private function maybe_handle_ajax() {
414
+ $result = [];
415
+
416
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
417
+ switch ( $_POST['action'] ) {
418
+ case 'elementor_update_site_name':
419
+ // If no value is passed for any reason, no need ot update the site name.
420
+ $result = $this->maybe_update_site_name();
421
+ break;
422
+ case 'elementor_update_site_logo':
423
+ $result = $this->maybe_update_site_logo();
424
+ break;
425
+ case 'elementor_upload_site_logo':
426
+ $result = $this->maybe_upload_logo_image();
427
+ break;
428
+ case 'elementor_activate_hello_theme':
429
+ $result = $this->maybe_activate_hello_theme();
430
+ break;
431
+ case 'elementor_upload_and_install_pro':
432
+ $result = $this->upload_and_install_pro();
433
+ break;
434
+ case 'elementor_update_onboarding_option':
435
+ $result = $this->maybe_update_onboarding_db_option();
436
+ }
437
+
438
+ if ( ! empty( $result ) ) {
439
+ if ( 'success' === $result['status'] ) {
440
+ wp_send_json_success( $result['payload'] );
441
+ } else {
442
+ wp_send_json_error( $result['payload'] );
443
+ }
444
+ }
445
+ }
446
+
447
+ public function __construct() {
448
+ add_action( 'elementor/init', function() {
449
+ // Only load when viewing the onboarding app.
450
+ if ( Plugin::$instance->app->is_current() ) {
451
+ $this->set_onboarding_settings();
452
+ // Needed for installing the Hello Elementor theme.
453
+ wp_enqueue_script( 'updates' );
454
+ // Needed for uploading Logo from WP Media Library.
455
+ wp_enqueue_media();
456
+
457
+ Plugin::$instance->app->set_settings( 'disable_dark_theme', true );
458
+ }
459
+ }, 12 );
460
+
461
+ // Needed for uploading Logo from WP Media Library. The 'admin_menu' hook is used because it runs before
462
+ // 'admin_init', and the App triggers printing footer scripts on 'admin_init' at priority 0.
463
+ add_action( 'admin_menu', function() {
464
+ add_action( 'wp_print_footer_scripts', 'wp_print_media_templates' );
465
+ } );
466
+
467
+ add_action( 'admin_init', function() {
468
+ if ( wp_doing_ajax() &&
469
+ isset( $_POST['action'] ) &&
470
+ isset( $_POST['_nonce'] ) &&
471
+ wp_verify_nonce( $_POST['_nonce'], Ajax::NONCE_KEY ) &&
472
+ current_user_can( 'manage_options' )
473
+ ) {
474
+ $this->maybe_handle_ajax();
475
+ }
476
+ } );
477
+ }
478
+ }
app/modules/site-editor/assets/js/context/template-types.js ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const Context = React.createContext();
2
+
3
+ import '../../scss/loading.scss';
4
+
5
+ class TemplateTypesContext extends React.Component {
6
+ static propTypes = {
7
+ children: PropTypes.object.isRequired,
8
+ };
9
+
10
+ constructor( props ) {
11
+ super( props );
12
+ this.state = {
13
+ templateTypes: [],
14
+ loading: true,
15
+ error: false,
16
+ };
17
+ }
18
+
19
+ componentDidMount() {
20
+ this.getTemplateTypes()
21
+ .then( ( response ) => {
22
+ this.setState( {
23
+ templateTypes: response,
24
+ loading: false,
25
+ } );
26
+ } )
27
+ .fail( ( error ) => {
28
+ this.setState( {
29
+ error: error.statusText ? error.statusText : error,
30
+ loading: false,
31
+ } );
32
+ } );
33
+ }
34
+
35
+ getTemplateTypes() {
36
+ return elementorCommon.ajax.load( {
37
+ action: 'app_site_editor_template_types',
38
+ } );
39
+ }
40
+
41
+ render() {
42
+ if ( this.state.error ) {
43
+ return <div className="e-loading-wrapper"><h3>{ __( 'Error:', 'elementor' ) } { this.state.error }</h3></div>;
44
+ }
45
+
46
+ if ( this.state.loading ) {
47
+ return (
48
+ <div className="elementor-loading">
49
+ <div className="elementor-loader-wrapper">
50
+ <div className="elementor-loader">
51
+ <div className="elementor-loader-boxes">
52
+ <div className="elementor-loader-box" />
53
+ <div className="elementor-loader-box" />
54
+ <div className="elementor-loader-box" />
55
+ <div className="elementor-loader-box" />
56
+ </div>
57
+ </div>
58
+ <div className="elementor-loading-title">{ __( 'Loading', 'elementor' ) }</div>
59
+ </div>
60
+ </div>
61
+ );
62
+ }
63
+
64
+ return (
65
+ <Context.Provider value={ this.state }>
66
+ { this.props.children }
67
+ </Context.Provider>
68
+ );
69
+ }
70
+ }
71
+
72
+ export const TemplateTypesConsumer = Context.Consumer;
73
+ export default TemplateTypesContext;
app/modules/site-editor/assets/js/module.js ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import router from '@elementor/router';
2
+ import SiteEditorPromotion from './pages/promotion';
3
+ import NotFound from './pages/not-found';
4
+
5
+ export default class SiteEditor {
6
+ constructor() {
7
+ this.saveTemplateTypesToCache();
8
+
9
+ router.addRoute( {
10
+ path: '/site-editor/promotion',
11
+ component: SiteEditorPromotion,
12
+ } );
13
+
14
+ router.addRoute( {
15
+ path: '/site-editor/*',
16
+ component: NotFound,
17
+ } );
18
+ }
19
+
20
+ saveTemplateTypesToCache() {
21
+ const types = this.getTypes();
22
+ elementorCommon.ajax.addRequestCache( {
23
+ unique_id: 'app_site_editor_template_types',
24
+ }, types );
25
+ }
26
+
27
+ getTypes() {
28
+ return [
29
+ {
30
+ type: 'header',
31
+ icon: 'eicon-header',
32
+ title: __( 'Header', 'elementor' ),
33
+ urls: {
34
+ thumbnail: elementorAppConfig.assets_url + '/images/app/site-editor/header.svg',
35
+ },
36
+ tooltip_data: {
37
+ title: __( 'What is a Header Template?', 'elementor' ),
38
+ content: __( 'The header template allows you to easily design and edit custom WordPress headers so you are no longer constrained by your theme’s header design limitations.', 'elementor' ),
39
+ tip: __( 'You can create multiple headers, and assign each to different areas of your site.', 'elementor' ),
40
+ docs: 'https://go.elementor.com/app-theme-builder-header/',
41
+ video_url: 'https://www.youtube.com/embed/HHy5RK6W-6I',
42
+ },
43
+ },
44
+ {
45
+ type: 'footer',
46
+ icon: 'eicon-footer',
47
+ title: __( 'Footer', 'elementor' ),
48
+ urls: {
49
+ thumbnail: elementorAppConfig.assets_url + '/images/app/site-editor/footer.svg',
50
+ },
51
+ tooltip_data: {
52
+ title: __( 'What is a Footer Template?', 'elementor' ),
53
+ content: __( 'The footer template allows you to easily design and edit custom WordPress footers without the limits of your theme’s footer design constraints', 'elementor' ),
54
+ tip: __( 'You can create multiple footers, and assign each to different areas of your site.', 'elementor' ),
55
+ docs: 'https://go.elementor.com/app-theme-builder-footer/',
56
+ video_url: 'https://www.youtube.com/embed/xa8DoR4tQrY',
57
+ },
58
+ },
59
+ {
60
+ type: 'single-page',
61
+ icon: 'eicon-single-page',
62
+ title: __( 'Single Page', 'elementor' ),
63
+ urls: {
64
+ thumbnail: elementorAppConfig.assets_url + '/images/app/site-editor/single-page.svg',
65
+ },
66
+ tooltip_data: {
67
+ title: __( 'What is a Single Page Template?', 'elementor' ),
68
+ content: __( 'A single page template allows you to easily create the layout and style of pages, ensuring design consistency across all the pages of your site.', 'elementor' ),
69
+ tip: __( 'You can create multiple single page templates, and assign each to different areas of your site.', 'elementor' ),
70
+ docs: 'https://go.elementor.com/app-theme-builder-page/',
71
+ video_url: 'https://www.youtube.com/embed/_y5eZ60lVoY',
72
+ },
73
+ },
74
+ {
75
+ type: 'single-post',
76
+ icon: 'eicon-single-post',
77
+ title: __( 'Single Post', 'elementor' ),
78
+ urls: {
79
+ thumbnail: elementorAppConfig.assets_url + '/images/app/site-editor/single-post.svg',
80
+ },
81
+ tooltip_data: {
82
+ title: __( 'What is a Single Post Template?', 'elementor' ),
83
+ content: __( 'A single post template allows you to easily design the layout and style of posts, ensuring a design consistency throughout all your blog posts, for example.', 'elementor' ),
84
+ tip: __( 'You can create multiple single post templates, and assign each to a different category.', 'elementor' ),
85
+ docs: 'https://go.elementor.com/app-theme-builder-post/',
86
+ video_url: 'https://www.youtube.com/embed/8Fk-Edu7DL0',
87
+ },
88
+ },
89
+ {
90
+ type: 'archive',
91
+ icon: 'eicon-archive',
92
+ title: __( 'Archive', 'elementor' ),
93
+ urls: {
94
+ thumbnail: elementorAppConfig.assets_url + '/images/app/site-editor/archive.svg',
95
+ },
96
+ tooltip_data: {
97
+ title: __( 'What is an Archive Template?', 'elementor' ),
98
+ content: __( 'An archive template allows you to easily design the layout and style of archive pages - those pages that show a list of posts (e.g. a blog’s list of recent posts), which may be filtered by terms such as authors, categories, tags, search results, etc.', 'elementor' ),
99
+ tip: __( 'If you’d like a different style for a specific category, it’s easy to create a separate archive template whose condition is to only display when users are viewing that category’s list of posts.', 'elementor' ),
100
+ docs: 'https://go.elementor.com/app-theme-builder-archive/',
101
+ video_url: 'https://www.youtube.com/embed/wxElpEh9bfA',
102
+ },
103
+ },
104
+ {
105
+ type: 'search-results',
106
+ icon: 'eicon-search-results',
107
+ title: __( 'search results page', 'elementor' ),
108
+ urls: {
109
+ thumbnail: elementorAppConfig.assets_url + '/images/app/site-editor/search-results.svg',
110
+ },
111
+ tooltip_data: {
112
+ title: __( 'What is a Search Results Template?', 'elementor' ),
113
+ content: __( 'You can easily control the layout and design of the Search Results page with the Search Results template, which is simply a special archive template just for displaying search results.', 'elementor' ),
114
+ tip: __( 'You can customize the message if there are no results for the search term.', 'elementor' ),
115
+ docs: 'https://go.elementor.com/app-theme-builder-search-results/',
116
+ video_url: 'https://www.youtube.com/embed/KKkIU_L5sDo',
117
+ },
118
+ },
119
+ {
120
+ type: 'product',
121
+ icon: 'eicon-single-product',
122
+ title: __( 'Product', 'elementor' ),
123
+ urls: {
124
+ thumbnail: elementorAppConfig.assets_url + '/images/app/site-editor/product.svg',
125
+ },
126
+ tooltip_data: {
127
+ title: __( 'What is a Single Product Template?', 'elementor' ),
128
+ content: __( 'A single product template allows you to easily design the layout and style of WooCommerce single product pages, and apply that template to various conditions that you assign.', 'elementor' ),
129
+ tip: __( 'You can create multiple single product templates, and assign each to different types of products, enabling a custom design for each group of similar products.', 'elementor' ),
130
+ docs: 'https://go.elementor.com/app-theme-builder-product/',
131
+ video_url: 'https://www.youtube.com/embed/PjhoB1RWkBM',
132
+ },
133
+ },
134
+ {
135
+ type: 'products',
136
+ icon: 'eicon-products',
137
+ title: __( 'Products Archive', 'elementor' ),
138
+ urls: {
139
+ thumbnail: elementorAppConfig.assets_url + '/images/app/site-editor/products.svg',
140
+ },
141
+ tooltip_data: {
142
+ title: __( 'What is a Products Archive Template?', 'elementor' ),
143
+ content: __( 'A products archive template allows you to easily design the layout and style of your WooCommerce shop page or other product archive pages - those pages that show a list of products, which may be filtered by terms such as categories, tags, etc.', 'elementor' ),
144
+ tip: __( 'You can create multiple archive product templates, and assign each to different categories of products. This gives you the freedom to customize the appearance for each type of product being shown.', 'elementor' ),
145
+ docs: 'https://go.elementor.com/app-theme-builder-products-archive/',
146
+ video_url: 'https://www.youtube.com/embed/cQLeirgkguA',
147
+ },
148
+ },
149
+ {
150
+ type: 'error-404',
151
+ icon: 'eicon-error-404',
152
+ title: __( '404 page', 'elementor' ),
153
+ urls: {
154
+ thumbnail: elementorAppConfig.assets_url + '/images/app/site-editor/error-404.svg',
155
+ },
156
+ tooltip_data: {
157
+ title: __( 'What is a 404 Page Template?', 'elementor' ),
158
+ content: __( 'A 404 page template allows you to easily design the layout and style of the page that is displayed when a visitor arrives at a page that does not exist.', 'elementor' ),
159
+ tip: __( 'Keep your site\'s visitors happy when they get lost by displaying your recent posts, a search bar, or any information that might help the user find what they were looking for.', 'elementor' ),
160
+ docs: 'https://go.elementor.com/app-theme-builder-404/',
161
+ video_url: 'https://www.youtube.com/embed/ACCNp9tBMQg',
162
+ },
163
+ },
164
+ ];
165
+ }
166
+ }
app/modules/site-editor/assets/js/molecules/site-part.js ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Card from 'elementor-app/ui/card/card';
2
+ import CardHeader from 'elementor-app/ui/card/card-header';
3
+ import CardBody from 'elementor-app/ui/card/card-body';
4
+ import CardImage from 'elementor-app/ui/card/card-image';
5
+ import Heading from 'elementor-app/ui/atoms/heading';
6
+
7
+ import './site-part.scss';
8
+
9
+ export default function SitePart( props ) {
10
+ return (
11
+ <Card className="e-site-part">
12
+ <CardHeader>
13
+ <Heading tag="h1" variant="text-sm" className="eps-card__headline">{ props.title }</Heading>
14
+ { props.actionButton }
15
+ </CardHeader>
16
+ <CardBody>
17
+ <CardImage alt={ props.title } src={ props.thumbnail }>
18
+ { props.children }
19
+ </CardImage>
20
+ </CardBody>
21
+ </Card>
22
+ );
23
+ }
24
+
25
+ SitePart.propTypes = {
26
+ thumbnail: PropTypes.string.isRequired,
27
+ title: PropTypes.string.isRequired,
28
+ children: PropTypes.object,
29
+ showIndicator: PropTypes.bool,
30
+ actionButton: PropTypes.object,
31
+ };
app/modules/site-editor/assets/js/molecules/site-part.scss ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $dark-svg-filter: invert(0.8) sepia(1) saturate(0.2) hue-rotate(180deg) contrast(1.25) brightness(1.2);
2
+
3
+ $info-toggle-color: tints(300);
4
+ $info-toggle-hover-color: tints(500);
5
+
6
+ :root {
7
+ --info-toggle-color: #{$info-toggle-color};
8
+ --info-toggle-hover-color: #{$info-toggle-hover-color};
9
+ }
10
+
11
+ $info-toggle-dark-color: dark-tints(400);
12
+ $info-toggle-dark-hover-color: dark-tints(200);
13
+
14
+ .eps-theme-dark {
15
+ --placeholder-filter: #{$dark-svg-filter};
16
+ --info-toggle-color: #{$info-toggle-dark-color};
17
+ --info-toggle-hover-color: #{$info-toggle-dark-hover-color};
18
+ }
19
+
20
+ .e-site-part {
21
+ .#{$eps-prefix}card__image {
22
+ filter: var(--placeholder-filter, none);
23
+ }
24
+
25
+ &__info-toggle {
26
+ color: var(--info-toggle-color);
27
+
28
+ &:hover {
29
+ --info-toggle-color: var(--info-toggle-hover-color);
30
+ }
31
+ }
32
+ }
app/modules/site-editor/assets/js/organisms/all-parts-button.js ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import MenuItem from 'elementor-app/ui/menu/menu-item';
2
+ import { Match } from '@reach/router';
3
+
4
+ export default function AllPartsButton( props ) {
5
+ const activePathname = '/site-editor/templates';
6
+
7
+ return (
8
+ <Match path={ activePathname }>
9
+ { ( { match } ) => {
10
+ const className = `eps-menu-item__link${ match || props.promotion ? ' eps-menu-item--active' : '' }`;
11
+
12
+ return (
13
+ <MenuItem
14
+ text={ __( 'All Parts', 'elementor' ) }
15
+ className={ className }
16
+ icon="eicon-filter"
17
+ url={ props.url }
18
+ /> );
19
+ }
20
+ }
21
+ </Match>
22
+ );
23
+ }
24
+
25
+ AllPartsButton.propTypes = {
26
+ url: PropTypes.string,
27
+ promotion: PropTypes.bool,
28
+ };
app/modules/site-editor/assets/js/organisms/menu.js ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import UiMenu from 'elementor-app/ui/menu/menu';
2
+ import { Context as TemplateTypesContext } from '../context/template-types';
3
+ import Button from 'elementor-app/ui/molecules/button';
4
+ import AddNewButton from 'elementor-app/ui/molecules/add-new-button';
5
+
6
+ import './menu.scss';
7
+
8
+ export default function Menu( props ) {
9
+ const { templateTypes } = React.useContext( TemplateTypesContext ),
10
+ actionButton = ( itemProps ) => {
11
+ const className = 'eps-menu-item__action-button';
12
+
13
+ if ( props.promotion ) {
14
+ return <Button text={ __( 'Go Pro', 'elementor' ) } hideText icon="eicon-lock" className={ className } />;
15
+ }
16
+
17
+ const goToCreate = () => {
18
+ location.href = itemProps.urls.create;
19
+ };
20
+
21
+ return (
22
+ <span className={ className }>
23
+ <AddNewButton hideText={ true } size="sm" onClick={ () => goToCreate() } />
24
+ </span>
25
+ );
26
+ };
27
+
28
+ return (
29
+ <UiMenu menuItems={ templateTypes } actionButton={ actionButton } promotion={ props.promotion }>
30
+ { props.allPartsButton }
31
+ <div className="eps-menu__title">
32
+ { __( 'Site Parts', 'elementor' ) }
33
+ </div>
34
+ </UiMenu>
35
+ );
36
+ }
37
+
38
+ Menu.propTypes = {
39
+ allPartsButton: PropTypes.element.isRequired,
40
+ promotion: PropTypes.bool,
41
+ };
app/modules/site-editor/assets/js/organisms/menu.scss ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ .#{$eps-prefix}menu__title {
2
+ margin-top: spacing(44);
3
+ margin-bottom: spacing(16);
4
+ }
app/modules/site-editor/assets/js/organisms/site-parts.js ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable jsx-a11y/iframe-has-title */
2
+ import Button from 'elementor-app/ui/molecules/button';
3
+ import CssGrid from 'elementor-app/ui/atoms/css-grid';
4
+ import ModalProvider from 'elementor-app/ui/modal/modal';
5
+ import SitePart from '../molecules/site-part';
6
+
7
+ import { Context as TemplateTypesContext } from '../context/template-types';
8
+
9
+ const InfoButton = ( props ) => {
10
+ const toggleButtonProps = {
11
+ text: __( 'Info', 'elementor' ),
12
+ hideText: true,
13
+ icon: 'eicon-info-circle e-site-part__info-toggle',
14
+ };
15
+
16
+ return (
17
+ <ModalProvider toggleButtonProps={ toggleButtonProps } title={ props.title }>
18
+ <CssGrid columns={ 2 } spacing={ 60 }>
19
+ <section>
20
+ <h3>{ props.type }</h3>
21
+ <p>
22
+ { props.content }<br />
23
+ <Button text={ __( 'Learn More', 'elementor' ) } color="link" target="_blank" url={ props.docs } />
24
+ </p>
25
+ <div className="eps-modal__tip">
26
+ <h3>{ __( 'Tip', 'elementor' ) }</h3>
27
+ <p>{ props.tip }</p>
28
+ </div>
29
+ </section>
30
+ <section>
31
+ <h3>{ __( 'Watch Video', 'elementor' ) }</h3>
32
+ <div className="video-wrapper">
33
+ <iframe id="ytplayer" src={ props.video_url } frameBorder="0" />
34
+ </div>
35
+ </section>
36
+ </CssGrid>
37
+ </ModalProvider>
38
+ );
39
+ };
40
+
41
+ InfoButton.propTypes = {
42
+ content: PropTypes.string.isRequired,
43
+ docs: PropTypes.string.isRequired,
44
+ tip: PropTypes.string.isRequired,
45
+ title: PropTypes.string.isRequired,
46
+ type: PropTypes.string.isRequired,
47
+ video_url: PropTypes.string.isRequired,
48
+ };
49
+
50
+ export default function SiteParts( props ) {
51
+ const { templateTypes } = React.useContext( TemplateTypesContext );
52
+
53
+ return (
54
+ <CssGrid className="e-site-editor__site-parts" colMinWidth={ 200 } spacing={ 25 }>
55
+ { (
56
+ templateTypes.map( ( item ) => (
57
+ <SitePart className="e-site-editor__site-part" actionButton={ <InfoButton type={ item.title }{ ...item.tooltip_data } /> } thumbnail={ item.urls.thumbnail } key={ item.type } { ...item }>
58
+ { React.createElement( props.hoverElement, item ) }
59
+ </SitePart>
60
+ ) )
61
+ ) }
62
+ </CssGrid>
63
+ );
64
+ }
65
+
66
+ SiteParts.propTypes = {
67
+ hoverElement: PropTypes.func.isRequired,
68
+ };
app/modules/site-editor/assets/js/package.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Alphabetical order.
2
+ import AllPartsButton from './organisms/all-parts-button';
3
+ import Layout from './templates/layout';
4
+ import Module from './module';
5
+ import NotFound from './pages/not-found';
6
+ import SiteParts from './organisms/site-parts';
7
+ import SitePart from './molecules/site-part';
8
+ import { Context as TemplateTypesContext } from './context/template-types';
9
+
10
+ export default {
11
+ AllPartsButton,
12
+ Layout,
13
+ Module,
14
+ NotFound,
15
+ SitePart,
16
+ SiteParts,
17
+ TemplateTypesContext,
18
+ };
app/modules/site-editor/assets/js/pages/not-found.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Dialog from 'elementor-app/ui/dialog/dialog';
2
+
3
+ export default function NotFound() {
4
+ const url = React.useMemo( () => ( elementorAppConfig.menu_url.split( '#' )?.[ 1 ] || '/site-editor' ), [] );
5
+
6
+ return (
7
+ <Dialog
8
+ title={ __( 'Theme Builder could not be loaded', 'elementor' ) }
9
+ text={ __( 'We’re sorry, but something went wrong. Click on ‘Learn more’ and follow each of the steps to quickly solve it.', 'elementor' ) }
10
+ approveButtonUrl="https://go.elementor.com/app-theme-builder-load-issue/"
11
+ approveButtonColor="link"
12
+ approveButtonTarget="_blank"
13
+ approveButtonText={ __( 'Learn More', 'elementor' ) }
14
+ dismissButtonText={ __( 'Go Back', 'elementor' ) }
15
+ dismissButtonUrl={ url }
16
+ />
17
+ );
18
+ }
app/modules/site-editor/assets/js/pages/promotion.js ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import AllPartsButton from '../organisms/all-parts-button';
2
+ import Button from 'elementor-app/ui/molecules/button';
3
+ import CardOverlay from 'elementor-app/ui/card/card-overlay';
4
+ import Grid from 'elementor-app/ui/grid/grid';
5
+ import Heading from 'elementor-app/ui/atoms/heading';
6
+ import Layout from '../templates/layout';
7
+ import SiteParts from '../organisms/site-parts';
8
+ import Text from 'elementor-app/ui/atoms/text';
9
+
10
+ import './promotion.scss';
11
+
12
+ export default function Promotion() {
13
+ const promotionUrl = 'https://go.elementor.com/go-pro-theme-builder/',
14
+ PromotionHoverElement = ( props ) => {
15
+ const promotionUrlWithType = `${ promotionUrl }?type=${ props.type }`;
16
+ return (
17
+ <CardOverlay className="e-site-editor__promotion-overlay">
18
+ <a className="e-site-editor__promotion-overlay__link" target="_blank" rel="noopener noreferrer" href={ promotionUrlWithType }>
19
+ <i className="e-site-editor__promotion-overlay__icon eicon-lock" />
20
+ <Button size="sm" color="cta" variant="contained" text={ __( 'Upgrade', 'elementor' ) } />
21
+ </a>
22
+ </CardOverlay>
23
+ );
24
+ };
25
+
26
+ PromotionHoverElement.propTypes = {
27
+ className: PropTypes.string,
28
+ type: PropTypes.string.isRequired,
29
+ };
30
+
31
+ return (
32
+ <Layout allPartsButton={ <AllPartsButton promotion /> } promotion>
33
+ <section className="e-site-editor__promotion">
34
+ <Grid container className="page-header">
35
+ <Grid item sm={ 7 } justify="end">
36
+ <Heading variant="h1">
37
+ { __( 'Customize every part of your site', 'elementor' ) }
38
+ </Heading>
39
+ <Text>
40
+ { __( 'Get total control, consistency and a faster workflow by designing the recurring parts that make up a complete website like the Header & Footer, Archive, 404, WooCommerce pages and more.', 'elementor' ) }
41
+ </Text>
42
+ </Grid>
43
+ <Grid item container justify="end" alignItems="start" sm={ 5 }>
44
+ <Button
45
+ size="sm"
46
+ color="cta"
47
+ variant="contained"
48
+ url={ promotionUrl }
49
+ target="_blank"
50
+ text={ __( 'Upgrade Now', 'elementor' ) }
51
+ />
52
+ </Grid>
53
+ </Grid>
54
+ <hr className="eps-separator" />
55
+ <SiteParts hoverElement={ PromotionHoverElement } />
56
+ </section>
57
+ </Layout>
58
+ );
59
+ }
app/modules/site-editor/assets/js/pages/promotion.scss ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $promotion-overlay-icon-size: spacing(20);
2
+ $promotion-overlay-icon-spacing: spacing(16);
3
+ $promotion-overlay-icon-color: theme-colors(light);
4
+
5
+ .e-site-editor__promotion-overlay {
6
+ &__link {
7
+ display: flex;
8
+ width: 100%;
9
+ height: 100%;
10
+ align-items: center;
11
+ justify-content: center;
12
+ flex-direction: column;
13
+ text-decoration: none;
14
+ }
15
+
16
+ &__icon {
17
+ font-size: $promotion-overlay-icon-size;
18
+ color: $promotion-overlay-icon-color;
19
+ margin-bottom: $promotion-overlay-icon-spacing;
20
+ }
21
+ }
app/modules/site-editor/assets/js/templates/layout.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Page from 'elementor-app/layout/page';
2
+ import Menu from '../organisms/menu';
3
+ import TemplateTypesContext from '../context/template-types';
4
+
5
+ import './site-editor.scss';
6
+
7
+ export default function Layout( props ) {
8
+ const config = {
9
+ title: __( 'Theme Builder', 'elementor' ),
10
+ titleRedirectRoute: props.titleRedirectRoute ?? null,
11
+ headerButtons: props.headerButtons,
12
+ sidebar: <Menu allPartsButton={ props.allPartsButton } promotion={ props.promotion } />,
13
+ content: props.children,
14
+ };
15
+
16
+ return (
17
+ <TemplateTypesContext>
18
+ <Page { ...config } />
19
+ </TemplateTypesContext>
20
+ );
21
+ }
22
+
23
+ Layout.propTypes = {
24
+ headerButtons: PropTypes.arrayOf( PropTypes.object ),
25
+ allPartsButton: PropTypes.element.isRequired,
26
+ children: PropTypes.object.isRequired,
27
+ promotion: PropTypes.bool,
28
+ titleRedirectRoute: PropTypes.string,
29
+ };
30
+
31
+ Layout.defaultProps = {
32
+ headerButtons: [],
33
+ };
app/modules/site-editor/assets/js/templates/site-editor.scss ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ .e-site-editor {
2
+ &__header {
3
+ margin-bottom: spacing(44);
4
+ border-bottom: 1px solid var(--hr-color);
5
+ }
6
+ }
app/modules/site-editor/assets/scss/admin/admin-bar.scss ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ /* Will select next element after last '.elementor-general-section' */
2
+ #wpadminbar #wp-admin-bar-elementor_edit_page .elementor-general-section + .elementor-second-section {
3
+ border-top: 1px solid #464b50;
4
+ margin-top: 6px;
5
+ }
app/modules/site-editor/assets/scss/loading.scss ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $e-elementor-loader-container-background-color: tints(200);
2
+ $e-elementor-loader-background-color: rgba(theme-colors(light), .9);
3
+
4
+ :root {
5
+ --e-elementor-loader-container-background-color: #{$e-elementor-loader-container-background-color};
6
+ --e-elementor-loader-background-color: #{$e-elementor-loader-background-color};
7
+ }
8
+
9
+ $e-elementor-loader-dark-background-color: dark-tints(500);
10
+ $e-elementor-loader-container-dark-background-color: dark-tints(700);
11
+
12
+ .eps-theme-dark {
13
+ --e-elementor-loader-container-background-color: #{$e-elementor-loader-container-dark-background-color};
14
+ --e-elementor-loader-background-color: #{$e-elementor-loader-dark-background-color};
15
+ }
16
+
17
+ .elementor-loading {
18
+ background-color: var(--e-elementor-loader-container-background-color);
19
+ height: 100vh;
20
+ }
21
+
22
+ .elementor-loader-wrapper {
23
+ @include absolute-center;
24
+ width: 300px;
25
+ display: flex;
26
+ flex-wrap: wrap;
27
+ justify-content: center;
28
+ }
29
+
30
+ .elementor-loader {
31
+ border-radius: 50%;
32
+ padding: 40px;
33
+ height: 150px;
34
+ width: 150px;
35
+ background-color: var(--e-elementor-loader-background-color);
36
+ box-sizing: border-box; // For admin
37
+ box-shadow: 2px 2px 20px 4px rgba(0, 0, 0, .02);
38
+ }
39
+
40
+ .elementor-loader-boxes {
41
+ height: 100%;
42
+ width: 100%;
43
+ position: relative;
44
+ }
45
+
46
+ .elementor-loader-box {
47
+ $animation-time: 1.8s;
48
+
49
+ position: absolute;
50
+ background-color: $editor-lightest;
51
+ animation: load $animation-time linear infinite;
52
+
53
+ &:nth-of-type(1) {
54
+ width: 20%;
55
+ height: 100%;
56
+ left: 0;
57
+ top: 0;
58
+ }
59
+
60
+ &:not(:nth-of-type(1)) {
61
+ right: 0;
62
+ height: 20%;
63
+ width: 60%;
64
+ }
65
+
66
+ &:nth-of-type(2) {
67
+ top: 0;
68
+ animation-delay: calc( #{$animation-time} / 4 * -1 );
69
+ }
70
+
71
+ &:nth-of-type(3) {
72
+ top: 40%;
73
+ animation-delay: calc( #{$animation-time} / 4 * -2 );
74
+ }
75
+
76
+ &:nth-of-type(4) {
77
+ bottom: 0;
78
+ animation-delay: calc( #{$animation-time} / 4 * -3 );
79
+ }
80
+ }
81
+
82
+ .elementor-loading-title {
83
+ $spacing: 7px;
84
+
85
+ color: $editor-light;
86
+ text-align: center;
87
+ text-transform: uppercase;
88
+ margin-top: 30px;
89
+ letter-spacing: $spacing;
90
+ text-indent: $spacing;
91
+ font-size: 10px;
92
+ width: 100%;
93
+ }
94
+
95
+ @keyframes load {
96
+ 0% { opacity: .3; }
97
+ 50% { opacity: 1; }
98
+ 100% { opacity: .3; }
99
+ }
{core/app → app}/modules/site-editor/module.php RENAMED
@@ -1,5 +1,5 @@
1
  <?php
2
- namespace Elementor\Core\App\Modules\SiteEditor;
3
 
4
  use Elementor\Core\Base\Module as BaseModule;
5
  use Elementor\Plugin;
1
  <?php
2
+ namespace Elementor\App\Modules\SiteEditor;
3
 
4
  use Elementor\Core\Base\Module as BaseModule;
5
  use Elementor\Plugin;
{core/app → app}/view.php RENAMED
@@ -1,5 +1,5 @@
1
  <?php
2
- namespace Elementor\Core\App;
3
 
4
  use Elementor\Utils;
5
 
1
  <?php
2
+ namespace Elementor\App;
3
 
4
  use Elementor\Utils;
5
 
assets/css/admin-rtl.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
  .elementor-button {
3
  font-family: Roboto, Arial, Helvetica, Verdana, sans-serif;
4
  font-weight: 500;
@@ -387,7 +387,7 @@ body .block-editor #elementor-switch-mode .button i {
387
  font-size: 50px;
388
  color: #a4afb7;
389
  }
390
- .elementor-blank_state h2 {
391
  font-size: 32px;
392
  font-weight: 300;
393
  color: inherit;
@@ -832,7 +832,7 @@ fieldset:disabled a.e-button {
832
  display: block;
833
  position: absolute;
834
  top: 0;
835
- right: 1px;
836
  border: none;
837
  margin: 0;
838
  padding: 9px;
@@ -1156,9 +1156,15 @@ fieldset:disabled a.e-button {
1156
  display: none;
1157
  }
1158
 
1159
- #elementor_rollback > div,
1160
- #elementor_rollback_pro > div,
1161
  #elementor_replace_url > div {
 
 
 
 
 
 
 
 
1162
  display: -webkit-box;
1163
  display: -ms-flexbox;
1164
  display: flex;
@@ -1166,9 +1172,7 @@ fieldset:disabled a.e-button {
1166
  #elementor_rollback > div input,
1167
  #elementor_rollback > div select,
1168
  #elementor_rollback_pro > div input,
1169
- #elementor_rollback_pro > div select,
1170
- #elementor_replace_url > div input,
1171
- #elementor_replace_url > div select {
1172
  margin-left: 6px;
1173
  }
1174
 
@@ -1645,8 +1649,10 @@ fieldset:disabled a.e-button {
1645
  -webkit-box-align: start;
1646
  -ms-flex-align: start;
1647
  align-items: flex-start;
1648
- -ms-flex-wrap: wrap;
1649
- flex-wrap: wrap;
 
 
1650
  }
1651
  .e-experiment__title__indicator {
1652
  position: absolute;
@@ -1680,25 +1686,21 @@ fieldset:disabled a.e-button {
1680
  .e-experiment__table-title {
1681
  margin: 30px 0;
1682
  }
1683
- .e-experiment__status {
 
1684
  font-size: 0.9em;
 
1685
  font-weight: bold;
1686
  font-style: italic;
1687
  }
1688
  .e-experiment__button.button {
1689
  margin: 18px 0 22px 14px;
1690
  }
1691
- .e-experiment__dependency__title {
1692
- color: #495157;
1693
  }
1694
- .e-experiment__dependency__item {
1695
- margin: 0 1px;
1696
- border: 1px solid;
1697
- padding: 2px;
1698
- font-size: 13px;
1699
- color: #6d7882;
1700
- font-weight: bold;
1701
- background: #f1f3f5;
1702
  }
1703
 
1704
  #tab-experiments .form-table tr {
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
  .elementor-button {
3
  font-family: Roboto, Arial, Helvetica, Verdana, sans-serif;
4
  font-weight: 500;
387
  font-size: 50px;
388
  color: #a4afb7;
389
  }
390
+ .elementor-blank_state h3 {
391
  font-size: 32px;
392
  font-weight: 300;
393
  color: inherit;
832
  display: block;
833
  position: absolute;
834
  top: 0;
835
+ inset-inline-end: 1px;
836
  border: none;
837
  margin: 0;
838
  padding: 9px;
1156
  display: none;
1157
  }
1158
 
 
 
1159
  #elementor_replace_url > div {
1160
+ max-width: 800px;
1161
+ }
1162
+ #elementor_replace_url > div input {
1163
+ margin-bottom: 6px;
1164
+ }
1165
+
1166
+ #elementor_rollback > div,
1167
+ #elementor_rollback_pro > div {
1168
  display: -webkit-box;
1169
  display: -ms-flexbox;
1170
  display: flex;
1172
  #elementor_rollback > div input,
1173
  #elementor_rollback > div select,
1174
  #elementor_rollback_pro > div input,
1175
+ #elementor_rollback_pro > div select {
 
 
1176
  margin-left: 6px;
1177
  }
1178
 
1649
  -webkit-box-align: start;
1650
  -ms-flex-align: start;
1651
  align-items: flex-start;
1652
+ -webkit-box-orient: vertical;
1653
+ -webkit-box-direction: normal;
1654
+ -ms-flex-direction: column;
1655
+ flex-direction: column;
1656
  }
1657
  .e-experiment__title__indicator {
1658
  position: absolute;
1686
  .e-experiment__table-title {
1687
  margin: 30px 0;
1688
  }
1689
+ .e-experiment__dependency, .e-experiment__status {
1690
+ margin-top: 4px;
1691
  font-size: 0.9em;
1692
+ line-height: 18px;
1693
  font-weight: bold;
1694
  font-style: italic;
1695
  }
1696
  .e-experiment__button.button {
1697
  margin: 18px 0 22px 14px;
1698
  }
1699
+ .e-experiment__dependency {
1700
+ color: #21759b;
1701
  }
1702
+ .e-experiment__dependency__title {
1703
+ font-weight: inherit;
 
 
 
 
 
 
1704
  }
1705
 
1706
  #tab-experiments .form-table tr {
assets/css/admin-rtl.min.css CHANGED
@@ -1,2 +1,2 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
- .elementor-button{font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-weight:500;text-transform:uppercase;outline:none;border:none;border-radius:3px;-webkit-transition-property:background,color,opacity,-webkit-box-shadow;transition-property:background,color,opacity,-webkit-box-shadow;-o-transition-property:background,color,box-shadow,opacity;transition-property:background,color,box-shadow,opacity;transition-property:background,color,box-shadow,opacity,-webkit-box-shadow;-webkit-transition-duration:.3s;-o-transition-duration:.3s;transition-duration:.3s}.elementor-button:hover{border:none}.elementor-button:not([disabled]){cursor:pointer}.elementor-button:not(.elementor-button-state) .elementor-state-icon{display:none}.elementor-button.elementor-button-success{color:#fff}.elementor-button.elementor-button-success[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-success:not([disabled]){background-color:#39b54a}.elementor-button.elementor-button-success:not([disabled]):hover{opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-success:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-brand{color:#fff}.elementor-button.elementor-button-brand[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-brand:not([disabled]){background-color:#93003c}.elementor-button.elementor-button-brand:not([disabled]):hover{opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-brand:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-warning{background-color:#a4afb7;color:#fff}.elementor-button.elementor-button-warning[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-warning:not([disabled]):hover{background-color:#b01b1b;opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-warning:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-danger{background-color:#d72b3f;color:#fff}.elementor-button.elementor-button-danger[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-danger:not([disabled]):hover{opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-danger:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-edit-template{display:inline-block;margin-top:15px;color:#fff}.elementor-button.elementor-button-default{background-color:#a4afb7;color:#fff;font-size:11px;padding:7px 21px}.elementor-button.elementor-button-default:hover{background-color:#6d7882;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-default:active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-default:visited{color:#fff}.elementor-button.elementor-button-go-pro{background-color:#93003c}.elementor-button i{margin-left:10px}#adminmenu #toplevel_page_elementor div.wp-menu-image:before{content:"\e813";font-family:eicons;font-size:18px;margin-top:1px}#adminmenu #toplevel_page_elementor a[href="admin.php?page=go_elementor_pro"]{font-weight:700}#adminmenu #toplevel_page_elementor a[href="admin.php?page=go_elementor_pro"]:hover{color:#e0005b}#adminmenu #toplevel_page_elementor .dashicons.dashicons-star-filled{height:auto}#adminmenu #menu-posts-elementor_library .wp-menu-image:before{content:"\e8ff";font-family:eicons;font-size:18px}#e-admin-menu__kit-library{color:#5cb85c}body.admin-color-fresh #adminmenu #toplevel_page_elementor a[href="admin.php?page=go_elementor_pro"]{color:#c60051}.elementor-plugins-gopro{color:#93003c;text-shadow:1px 1px 1px #eee;font-weight:700}#elementor-switch-mode{margin:15px 0}#elementor-editor-button,#elementor-switch-mode-button{outline:none;cursor:pointer}#elementor-editor-button i,#elementor-switch-mode-button i{margin-left:3px;font-size:125%;font-style:normal}body.elementor-editor-active .elementor-switch-mode-off{display:none}body.elementor-editor-active #elementor-switch-mode-button{background-color:#f7f7f7;color:#555;border-color:#ccc;-webkit-box-shadow:0 1px 0 #ccc!important;box-shadow:0 1px 0 #ccc!important;text-shadow:unset}body.elementor-editor-active #elementor-switch-mode-button:hover{background-color:#e9e9e9}body.elementor-editor-active #elementor-switch-mode-button:active{-webkit-box-shadow:inset 0 1px 0 #ccc;box-shadow:inset 0 1px 0 #ccc;-webkit-transform:translateY(1px);-ms-transform:translateY(1px);transform:translateY(1px)}body.elementor-editor-active #postdivrich{display:none!important}body.elementor-editor-active .block-editor-block-list__layout,body.elementor-editor-active .editor-block-list__layout,body.elementor-editor-inactive #elementor-editor,body.elementor-editor-inactive .elementor-switch-mode-on{display:none}body.elementor-editor-active .edit-post-layout__content .edit-post-visual-editor{-ms-flex-preferred-size:auto;flex-basis:auto}body.elementor-editor-active #elementor-editor{margin-bottom:50px}body.elementor-editor-active .edit-post-text-editor__body .editor-post-text-editor{display:none}body .block-editor #elementor-switch-mode{margin:0 15px}body .block-editor #elementor-switch-mode .button{margin:2px;height:33px;font-size:13px;line-height:1}body .block-editor #elementor-switch-mode .button i{padding-left:5px}.elementor-button{font-size:13px;text-decoration:none;padding:15px 40px}#elementor-editor{height:300px;width:100%;-webkit-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease}#elementor-editor .elementor-loader-wrapper{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:300px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#elementor-editor .elementor-loader{border-radius:50%;padding:40px;height:150px;width:150px;background-color:hsla(0,0%,100%,.9);-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:2px 2px 20px 4px rgba(0,0,0,.02);box-shadow:2px 2px 20px 4px rgba(0,0,0,.02)}#elementor-editor .elementor-loader-boxes{height:100%;width:100%;position:relative}#elementor-editor .elementor-loader-box{position:absolute;background-color:#d5dadf;-webkit-animation:load 1.8s linear infinite;animation:load 1.8s linear infinite}#elementor-editor .elementor-loader-box:first-of-type{width:20%;height:100%;left:0;top:0}#elementor-editor .elementor-loader-box:not(:first-of-type){right:0;height:20%;width:60%}#elementor-editor .elementor-loader-box:nth-of-type(2){top:0;-webkit-animation-delay:calc(1.8s / 4 * -1);animation-delay:calc(1.8s / 4 * -1)}#elementor-editor .elementor-loader-box:nth-of-type(3){top:40%;-webkit-animation-delay:calc(1.8s / 4 * -2);animation-delay:calc(1.8s / 4 * -2)}#elementor-editor .elementor-loader-box:nth-of-type(4){bottom:0;-webkit-animation-delay:calc(1.8s / 4 * -3);animation-delay:calc(1.8s / 4 * -3)}#elementor-editor .elementor-loading-title{color:#a4afb7;text-align:center;text-transform:uppercase;margin-top:30px;letter-spacing:7px;text-indent:7px;font-size:10px;width:100%}#elementor-go-to-edit-page-link{height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid #ddd;background-color:#f7f7f7;text-decoration:none;position:relative;font-family:Sans-serif}#elementor-go-to-edit-page-link:hover{background-color:#fff}#elementor-go-to-edit-page-link:focus{-webkit-box-shadow:none;box-shadow:none}#elementor-go-to-edit-page-link.elementor-animate #elementor-editor-button,#elementor-go-to-edit-page-link:not(.elementor-animate) .elementor-loader-wrapper{display:none}.elementor-button-spinner:before{font:normal 20px/.5 dashicons;speak:none;display:inline-block;padding:0;top:8px;right:-4px;position:relative;vertical-align:top;content:"\f463"}.elementor-button-spinner.loading:before{-webkit-animation:rotation 1s linear infinite;animation:rotation 1s linear infinite}.elementor-button-spinner.success:before{content:"\f147";color:#46b450}.elementor-blank_state{padding:5em 0;margin:auto;max-width:520px;text-align:center;color:#6d7882;font-family:Roboto,sans-serif}.elementor-blank_state i{font-size:50px;color:#a4afb7}.elementor-blank_state h2{font-size:32px;font-weight:300;color:inherit;margin:40px 0 10px;line-height:1.2}.elementor-blank_state p{font-size:16px;font-weight:400;color:#a4afb7;margin-bottom:40px}.elementor-blank_state .elementor-button{display:inline-block}#available-widgets [class*=elementor-template] .widget-title:before{content:"\e813";font-family:eicons;font-size:17px}.elementor-settings-form-page{padding-top:30px}._elementor_settings_update_time,.elementor-settings-form-page:not(.elementor-active){display:none}#confirm_fa_migration_admin_modal .dialog-confirm-ok{color:#6d7882}body.post-type-attachment table.media .column-title .media-icon img[src$=".svg"]{width:100%}.e-major-update-warning{margin-bottom:5px;max-width:1000px;display:-webkit-box;display:-ms-flexbox;display:flex}.e-major-update-warning__separator{margin:15px -12px}.e-major-update-warning__icon{font-size:17px;margin-left:9px;margin-right:2px}.e-major-update-warning__title{font-weight:600;margin-bottom:10px}.e-major-update-warning+p{display:none}.notice-success .e-major-update-warning__separator{border:1px solid #46b450}.notice-success .e-major-update-warning__icon{color:#79ba49}.notice-warning .e-major-update-warning__separator{border:1px solid #ffb900}.notice-warning .e-major-update-warning__icon{color:#f56e28}.plugins table.e-compatibility-update-table tr{background:transparent}.plugins table.e-compatibility-update-table tr th{font-weight:600}.plugins table.e-compatibility-update-table tr td,.plugins table.e-compatibility-update-table tr th{min-width:250px;font-size:13px;background:transparent;-webkit-box-shadow:none;box-shadow:none;border:none;padding:5px 0 5px 15px}:root{--e-focus-color:rgba(0,115,170,0.4);--e-context-primary-color:#0073aa;--e-context-primary-color-dark:#005177;--e-context-primary-tint-4:rgba(0,115,170,0.4);--e-context-primary-tint-1:rgba(0,115,170,0.04);--e-context-success-color:#39b54a;--e-context-success-color-dark:#2d8e3a;--e-context-success-tint-4:rgba(57,181,74,0.4);--e-context-success-tint-1:rgba(57,181,74,0.04);--e-context-info-color:#71d7f7;--e-context-info-color-dark:#41c9f4;--e-context-info-tint-4:rgba(113,215,247,0.4);--e-context-info-tint-1:rgba(113,215,247,0.04);--e-context-warning-color:#fcb92c;--e-context-warning-color-dark:#f2a503;--e-context-warning-tint-4:rgba(252,185,44,0.4);--e-context-warning-tint-1:rgba(252,185,44,0.04);--e-context-error-color:#d72b3f;--e-context-error-color-dark:#ae2131;--e-context-error-tint-4:rgba(215,43,63,0.4);--e-context-error-tint-1:rgba(215,43,63,0.04);--e-context-cta-color:#93003c;--e-context-cta-color-dark:#600027;--e-context-cta-tint-4:rgba(147,0,60,0.4);--e-context-cta-tint-1:rgba(147,0,60,0.04)}.e-getting-started{max-width:900px;padding:2.5em 0;margin:auto;text-align:center}.e-getting-started__header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-shadow:0 0 8px rgba(0,0,0,.1);box-shadow:0 0 8px rgba(0,0,0,.1)}.e-getting-started__header .e-logo-wrapper{font-size:10px;margin-left:10px}.e-getting-started__title{padding:0 15px;font-weight:600;text-transform:uppercase;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.e-getting-started__skip{border-right:1px solid #eee;font-size:16px;color:inherit}.e-getting-started__skip i{padding:15px}.e-getting-started__content{padding:50px}.e-getting-started__content h2{font-size:2em;margin-top:0}.e-getting-started__content--narrow{max-width:500px;margin:auto}.e-getting-started__video{margin:40px 0 60px}.e-getting-started__video iframe{-webkit-box-shadow:10px 10px 20px rgba(0,0,0,.15);box-shadow:10px 10px 20px rgba(0,0,0,.15)}.e-getting-started__actions .button-primary{margin-left:20px}:root{--e-button-padding-y:0.4375rem;--e-button-padding-x:0.75rem;--e-button-font-size:0.8125rem;--e-button-font-weight:500;--e-button-line-height:0.9375rem;--e-button-border-radius:3px;--e-button-context-color:var(--e-context-primary-color);--e-button-context-color-dark:var(--e-context-primary-color-dark);--e-button-context-tint:var(--e-context-primary-tint-1)}.e-button{display:inline-block;font-weight:var(--e-button-font-weight);text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:#fff;border:0;text-decoration:none;background:var(--e-button-context-color);padding:var(--e-button-padding-y) var(--e-button-padding-x);font-size:var(--e-button-font-size);line-height:var(--e-button-line-height);border-radius:var(--e-button-border-radius);-webkit-transition:background-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;-o-transition:background-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out}.e-button:active,.e-button:focus,.e-button:hover{color:#fff;text-decoration:none;background:var(--e-button-context-color-dark)}.e-button.focus,.e-button:focus{outline:0;-webkit-box-shadow:0 0 0 2px var(--e-focus-color);box-shadow:0 0 0 2px var(--e-focus-color)}.e-button.disabled,.e-button:disabled{opacity:.5;-webkit-box-shadow:none;box-shadow:none}.e-button:not(:disabled):not(.disabled){cursor:pointer}.e-button:not(:disabled):not(.disabled).active:focus,.e-button:not(:disabled):not(.disabled):active:focus{-webkit-box-shadow:0 0 0 2px var(--e-focus-color);box-shadow:0 0 0 2px var(--e-focus-color)}.e-button--primary{--e-button-context-color:var(--e-context-primary-color);--e-button-context-color-dark:var(--e-context-primary-color-dark);--e-button-context-tint:var(--e-context-primary-tint-1);--e-focus-color:var(--e-context-primary-tint-4)}.e-button--success{--e-button-context-color:var(--e-context-success-color);--e-button-context-color-dark:var(--e-context-success-color-dark);--e-button-context-tint:var(--e-context-success-tint-1);--e-focus-color:var(--e-context-success-tint-4)}.e-button--info{--e-button-context-color:var(--e-context-info-color);--e-button-context-color-dark:var(--e-context-info-color-dark);--e-button-context-tint:var(--e-context-info-tint-1);--e-focus-color:var(--e-context-info-tint-4)}.e-button--warning{--e-button-context-color:var(--e-context-warning-color);--e-button-context-color-dark:var(--e-context-warning-color-dark);--e-button-context-tint:var(--e-context-warning-tint-1);--e-focus-color:var(--e-context-warning-tint-4)}.e-button--error{--e-button-context-color:var(--e-context-error-color);--e-button-context-color-dark:var(--e-context-error-color-dark);--e-button-context-tint:var(--e-context-error-tint-1);--e-focus-color:var(--e-context-error-tint-4)}.e-button--cta{--e-button-context-color:var(--e-context-cta-color);--e-button-context-color-dark:var(--e-context-cta-color-dark);--e-button-context-tint:var(--e-context-cta-tint-1);--e-focus-color:var(--e-context-cta-tint-4)}.e-button.e-button--outline{color:var(--e-button-context-color);background:none;border:1px solid}.e-button.e-button--outline:focus,.e-button.e-button--outline:hover{color:var(--e-button-context-color-dark);background:var(--e-button-context-tint)}.e-button.e-button--outline.disabled,.e-button.e-button--outline:disabled{color:var(--e-button-context-color-dark);background:#818a91}.e-button>i{line-height:inherit;height:var(--e-button-line-height);width:-webkit-min-content;width:-moz-min-content;width:min-content}.e-button>*+*{-webkit-margin-start:.5ch;margin-inline-start:.5ch}.e-button--link{color:var(--e-button-context-color);background-color:transparent}.e-button--link:focus,.e-button--link:hover{color:var(--e-button-context-color-dark);background:var(--e-button-context-tint)}.e-button--link.disabled,.e-button--link:disabled{color:#818a91}a.e-button.disabled,fieldset:disabled a.e-button{pointer-events:none}:root{--e-notice-bg:#fff;--e-notice-border-color:#ccd0d4;--e-notice-context-color:#93003c;--e-notice-context-tint:var(--e-context-cta-tint-1);--e-notice-box-shadow:0 1px 4px rgba(0,0,0,0.15);--e-notice-dismiss-color:#6d7882}.e-notice{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;background:var(--e-notice-bg);border:1px solid var(--e-notice-border-color);border-inline-start-width:4px;-webkit-box-shadow:var(--e-notice-box-shadow);box-shadow:var(--e-notice-box-shadow);margin:5px 20px 5px 2px}.e-notice.notice{padding:0}.e-notice:before{display:block;content:"";position:absolute;right:-4px;top:-1px;bottom:-1px;width:4px;background-color:var(--e-notice-context-color)}.e-notice--primary{--e-notice-context-color:var(--e-context-primary-color);--e-notice-context-color-dark:var(--e-context-primary-color-dark);--e-notice-context-tint:var(--e-context-primary-tint-1)}.e-notice--success{--e-notice-context-color:var(--e-context-success-color);--e-notice-context-color-dark:var(--e-context-success-color-dark);--e-notice-context-tint:var(--e-context-success-tint-1)}.e-notice--info{--e-notice-context-color:var(--e-context-info-color);--e-notice-context-color-dark:var(--e-context-info-color-dark);--e-notice-context-tint:var(--e-context-info-tint-1)}.e-notice--warning{--e-notice-context-color:var(--e-context-warning-color);--e-notice-context-color-dark:var(--e-context-warning-color-dark);--e-notice-context-tint:var(--e-context-warning-tint-1)}.e-notice--error{--e-notice-context-color:var(--e-context-error-color);--e-notice-context-color-dark:var(--e-context-error-color-dark);--e-notice-context-tint:var(--e-context-error-tint-1)}.e-notice--cta{--e-notice-context-color:var(--e-context-cta-color);--e-notice-context-color-dark:var(--e-context-cta-color-dark);--e-notice-context-tint:var(--e-context-cta-tint-1)}.e-notice--extended{--e-notice-is-extended:1}.e-notice--dismissible{padding-right:38px}.e-notice__aside{overflow:hidden;background-color:var(--e-notice-context-tint);width:calc(var(--e-notice-is-extended, 0) * 50px);text-align:center;padding-top:15px;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.e-notice__icon-wrapper{display:inline-block;font-size:.625rem;max-height:1.5rem;width:1.5rem;line-height:1.5rem;border-radius:100px;background:var(--e-notice-context-color);color:#fff;text-shadow:0 0 3px var(--e-notice-context-color-dark),0 0 1px var(--e-notice-context-color-dark),0 0 1px var(--e-notice-context-color-dark)}.e-notice__content{padding:20px}.e-notice__actions{display:-webkit-box;display:-ms-flexbox;display:flex}.e-notice__actions>*+*{-webkit-margin-start:8px;margin-inline-start:8px}.e-notice__dismiss{width:20px;height:20px;line-height:20px;font-size:.8125rem;text-align:center;background:none;display:block;position:absolute;top:0;right:1px;border:none;margin:0;padding:9px;cursor:pointer;font-style:normal}.e-notice__dismiss:before{font-family:eicons;display:inline-block;content:"\e87f";color:var(--e-notice-dismiss-color);width:20px;border-radius:20px;speak:none;text-align:center;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.e-notice__dismiss:active:before,.e-notice__dismiss:focus:before,.e-notice__dismiss:hover:before{font-weight:700}.e-notice__dismiss:focus:before{color:#fff;background:var(--e-notice-dismiss-color);outline:none}.e-notice__dismiss:focus{outline:none}.e-notice p{line-height:1.2;padding:0;margin:0}.e-notice p+.e-notice__actions{margin-top:1rem}.e-notice h3{font-size:1.0625rem;line-height:1.2;margin:0}.e-notice h3+p{margin-top:8px}.elementor-admin-alert{padding:15px;border-left:5px solid transparent;position:relative;font-size:12px;line-height:1.5;text-align:right}.elementor-admin-alert a{color:inherit}.elementor-admin-alert.elementor-alert-info{color:#31708f;background-color:#d9edf7;border-color:#bcdff1}.elementor-admin-alert.elementor-alert-success{color:#3c763d;background-color:#dff0d8;border-color:#cae6be}.elementor-admin-alert.elementor-alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#f9f0c3}.elementor-admin-alert.elementor-alert-danger{color:#a94442;background-color:#f2dede;border-color:#e8c4c4}#elementor-system-info{padding:15px}#elementor-system-info .elementor-system-info-section{margin-bottom:10px}#elementor-system-info .elementor-system-info-section>.elementor-system-info-report-name{padding-left:10px;border-bottom:1px solid #e1e1e1}#elementor-system-info .elementor-system-info-section .widefat{white-space:pre}#elementor-system-info .elementor-system-info-section .elementor-log-entries{white-space:pre-wrap}#elementor-system-info .elementor-system-info-section:not(.elementor-system-info-log) tbody td:first-child{width:300px}#elementor-system-info .elementor-system-info-report-name{text-transform:uppercase;font-size:14px;margin:0;line-height:2}#elementor-system-info .elementor-system-info-report-row{overflow:hidden;padding:5px 0}#elementor-system-info .elementor-system-info-report-row>*{float:left}#elementor-system-info .elementor-system-info-field-recommendation,#elementor-system-info .elementor-system-info-report-field{padding-left:10px;color:#7f7f7f}#elementor-system-info .elementor-system-info-report-fields{padding-left:20px}#elementor-system-info .elementor-system-info-plugin-name{color:#000}#elementor-system-info .elementor-system-info-plugin-properties{padding:10px}#elementor-system-info #elementor-system-info-raw-code{width:100%;height:200px}#elementor-system-info #elementor-system-info-raw-code-label{padding:5px;display:block}#elementor-system-info .elementor-warning td:first-child{border-right:3px solid #fcb92c}#elementor-system-info a.box-title-tool{font-size:80%;margin-right:15px;color:#818a91}#elementor-system-info a.box-title-tool:hover{text-decoration:underline}#elementor-system-info #elementor-usage-recalc{font-size:12px;color:#fff;background-color:#a4afb7;padding:4px 18px 5px;border-radius:3px}@-webkit-keyframes elementor-rotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes elementor-rotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}#elementor-deactivate-feedback-dialog-wrapper{display:none}#elementor-deactivate-feedback-modal .dialog-widget-content{width:550px}#elementor-deactivate-feedback-modal .dialog-header{padding:18px 15px;-webkit-box-shadow:0 0 8px rgba(0,0,0,.1);box-shadow:0 0 8px rgba(0,0,0,.1);text-align:right}#elementor-deactivate-feedback-modal .dialog-message{padding:30px 30px 0;text-align:right}#elementor-deactivate-feedback-modal .elementor-deactivate-feedback-dialog-input{float:right;margin:0 0 0 15px;-webkit-box-shadow:none;box-shadow:none}#elementor-deactivate-feedback-modal .elementor-deactivate-feedback-dialog-input:not(:checked)~.elementor-feedback-text{display:none}#elementor-deactivate-feedback-modal .elementor-deactivate-feedback-dialog-label{display:block;font-size:13px;color:#6d7882}#elementor-deactivate-feedback-modal .elementor-feedback-text{margin:10px 30px 0 0;padding:5px;font-size:13px;-webkit-box-shadow:none;box-shadow:none;background-color:#fff;width:92%}#elementor-deactivate-feedback-modal .dialog-buttons-wrapper{border-top:none;text-align:right;padding:20px 30px 30px;overflow:hidden}#elementor-deactivate-feedback-modal .dialog-submit{background-color:#93003c;border-radius:3px;color:#fff;line-height:1;padding:12px 20px;font-size:13px;width:180px;height:38px}#elementor-deactivate-feedback-modal .dialog-submit.elementor-loading:before{display:inline-block;content:"\f463";font:18px dashicons;-webkit-animation:elementor-rotation 2s linear infinite;animation:elementor-rotation 2s linear infinite}#elementor-deactivate-feedback-modal .dialog-skip{font-size:12px;color:#a4afb7;background:none;float:left;width:auto}#elementor-deactivate-feedback-modal[data-feedback-selected=elementor_pro] .elementor-feedback-text{color:#b01b1b;padding:0}#elementor-deactivate-feedback-modal[data-feedback-selected=elementor_pro] .dialog-submit{display:none}#elementor-deactivate-feedback-dialog-header i{color:#93003c;font-size:19px}#elementor-deactivate-feedback-dialog-header-title{font-size:15px;text-transform:uppercase;font-weight:700;padding-right:5px}#elementor-deactivate-feedback-dialog-form-caption{font-weight:700;font-size:15px;color:#495157;line-height:1.4}#elementor-deactivate-feedback-dialog-form-body{padding-top:30px}.elementor-deactivate-feedback-dialog-input-wrapper{line-height:1;overflow:hidden;margin-bottom:15px}#elementor-hidden-area{display:none}#elementor-import-template-trigger{cursor:pointer}#elementor-import-template-area{display:none;margin:50px 0 30px;text-align:center}#elementor-import-template-form{display:inline-block;margin-top:30px;padding:30px 50px;background-color:#fff;border:1px solid #e5e5e5}#elementor-import-template-title{font-size:18px;color:#555d66}.form-table:not(.elementor-maintenance-mode-is-enabled) .elementor-default-hide{display:none}.elementor-maintenance-mode-error{color:red;line-height:1.6;display:none}#tab-fontawesome4_migration.elementor-active~p.submit,#tab-import-export-kit.elementor-active~p.submit,#tab-replace_url.elementor-active~p.submit{display:none}#elementor_replace_url>div,#elementor_rollback>div,#elementor_rollback_pro>div{display:-webkit-box;display:-ms-flexbox;display:flex}#elementor_replace_url>div input,#elementor_replace_url>div select,#elementor_rollback>div input,#elementor_rollback>div select,#elementor_rollback_pro>div input,#elementor_rollback_pro>div select{margin-left:6px}.tab-import-export-kit__wrapper{margin:40px 0;max-width:700px}.tab-import-export-kit__container{background-color:#fff;font-size:16px;max-width:700px;padding:30px}.tab-import-export-kit__container:not(:first-child){margin-top:5px}.tab-import-export-kit__container p{color:#a4afb7;font-size:16px;margin:20px 0 25px}.tab-import-export-kit__info{font-size:14px}.tab-import-export-kit__container a:not(.elementor-button),.tab-import-export-kit__info a{color:#58d0f5;text-decoration:underline}.tab-import-export-kit__box{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.tab-import-export-kit__box h2{color:#6d7882;font-size:28px;font-weight:400;line-height:1;margin:0}.tab-import-export-kit__box .elementor-button.elementor-button-success{font-weight:700;padding:8px 16px;text-transform:none}#dashboard-widgets .e-dashboard-widget h3.e-heading{font-weight:600;margin-bottom:13px}#dashboard-widgets .e-dashboard-widget .e-divider_bottom{border-bottom:1px solid #eee;margin:0 -12px;padding:6px 12px}#dashboard-widgets .e-dashboard-widget .e-divider_top{border-top:1px solid #eee;margin:0 -12px;padding:6px 12px}#dashboard-widgets .e-dashboard-widget .e-news-feed-wrap .e-divider_top,#dashboard-widgets .e-dashboard-widget .e-quick-actions-wrap .e-divider_top{padding-top:18px;margin-top:18px}.e-dashboard-widget .dashicons{color:#606a73}.e-dashboard-widget ul.e-action-list li{margin-top:14px}.e-dashboard-widget ul.e-action-list li a{margin-right:5px}#e-dashboard-overview .dashicons{vertical-align:middle;font-size:17px}#e-dashboard-overview .e-overview__header{display:table;width:100%;-webkit-box-shadow:0 5px 8px rgba(0,0,0,.05);box-shadow:0 5px 8px rgba(0,0,0,.05);margin:0 -12px 8px;padding:0 12px 12px}#e-dashboard-overview .e-overview__create,#e-dashboard-overview .e-overview__logo,#e-dashboard-overview .e-overview__versions{display:table-cell;vertical-align:middle}#e-dashboard-overview .e-overview__logo{width:30px}#e-dashboard-overview .e-overview__versions{padding:0 10px;font-size:.9em;line-height:1.5}#e-dashboard-overview .e-overview__version{display:block}#e-dashboard-overview .e-overview__create{text-align:left}#e-dashboard-overview .e-overview__feed{font-size:14px;font-weight:500}#e-dashboard-overview .e-overview__post{margin-top:10px}#e-dashboard-overview .e-overview__post-link{display:inline-block}#e-dashboard-overview .e-overview__badge{background:#39b54a;color:#fff;font-size:.75em;padding:3px 6px;border-radius:3px;text-transform:uppercase}#e-dashboard-overview .e-overview__post-description{margin:0 0 1.5em}#e-dashboard-overview .e-overview__recently-edited li{color:#72777c}#e-dashboard-overview .e-overview__footer.e-divider_top{padding-top:12px;padding-bottom:0}#e-dashboard-overview .e-overview__footer ul{display:-webkit-box;display:-ms-flexbox;display:flex;list-style:none;margin:0;padding:0}#e-dashboard-overview .e-overview__footer ul li{padding:0 10px;margin:0;border-right:1px solid #ddd}#e-dashboard-overview .e-overview__footer ul li:first-child{padding-right:0;border:none}#e-dashboard-overview .e-overview__go-pro a{color:#93003c;font-weight:500}.post-type-elementor_library #elementor-template-library-tabs-wrapper{padding-top:2em;margin-bottom:2em}.post-type-elementor_library th#taxonomy-elementor_library_category{width:110px}#elementor-new-template-modal .dialog-message{max-height:70vh}#elementor-new-template-modal .e-hidden{display:none!important}#elementor-new-template-dialog-content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:100%;text-align:right;color:#6d7882}@media (max-width:1439px){#elementor-new-template-dialog-content{padding:0 50px}}@media (min-width:1440px){#elementor-new-template-dialog-content{padding:0 120px}}#elementor-new-template__description{width:35%;max-width:300px;padding-left:100px}#elementor-new-template__description__title{font-size:30px;color:#556068}#elementor-new-template__description__title span{font-weight:700}#elementor-new-template__description__content{font-size:16px;padding:30px 0}#elementor-new-template__take_a_tour{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:15px}#elementor-new-template__take_a_tour i{color:#93003c;font-size:30px}#elementor-new-template__take_a_tour a{color:#6d7882;padding-right:10px;text-decoration:none;font-weight:500}#elementor-new-template__form{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:440px;padding:55px;background-color:#fff;border-radius:3px;-webkit-box-shadow:0 2px 30px 0 rgba(0,0,0,.08);box-shadow:0 2px 30px 0 rgba(0,0,0,.08)}#elementor-new-template__form__title{font-size:23px;color:#556068}#elementor-new-template__form__template-type.elementor-form-field__select{max-width:none}#elementor-new-template__form__template-type-badge{position:absolute;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:2px;border-radius:2px;background-color:#f1f3f5;padding:4px;font-size:8px;font-weight:500;line-height:1;text-transform:uppercase;top:50%;inset-inline-end:28px;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}#elementor-new-template__form .elementor-form-field__label{display:block;margin:25px 0 7px;font-size:14px;line-height:1}#elementor-new-template__form .elementor-form-field input,#elementor-new-template__form .elementor-form-field select{width:100%;height:50px;padding:10px;font-size:14px;-webkit-box-shadow:none;box-shadow:none;border-radius:3px;background:none;color:#495157;border:1px solid;outline:none}#elementor-new-template__form .elementor-form-field input:not(:focus),#elementor-new-template__form .elementor-form-field select:not(:focus){border-color:#d5dadf}#elementor-new-template__form .elementor-form-field input:focus,#elementor-new-template__form .elementor-form-field select:focus{border-color:#a4afb7}#elementor-new-template__form .elementor-form-field__select{appearance:none;-webkit-appearance:none;-moz-appearance:none;cursor:pointer}#elementor-new-template__form .elementor-form-field__select__wrapper{position:relative}#elementor-new-template__form .elementor-form-field__select__wrapper:after{font-family:eicons;content:"\e8ad";position:absolute;top:50%;left:10px;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}#elementor-new-template__form__lock_button,#elementor-new-template__form__submit{display:block;width:100%;height:50px;margin-top:24px;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;text-transform:none}@media (max-width:1024px){#elementor-new-template__description{max-width:250px;padding-left:30px}}@media (max-width:767px){#elementor-new-template__description{display:none}}#elementor-role-manager{max-width:500px;margin-top:50px}#elementor-role-manager h3{color:#6d7882;font-weight:400;font-size:22px}#elementor-role-manager .elementor-settings-form-page{padding:0}#elementor-role-manager .elementor-role-row{background:#fff;color:#6d7882;margin-bottom:2px}#elementor-role-manager .elementor-role-row .elementor-role-label{display:-webkit-box;display:-ms-flexbox;display:flex;padding:15px 20px;font-weight:500;cursor:pointer}#elementor-role-manager .elementor-role-row .elementor-role-label span.elementor-role-name{padding-left:20px}#elementor-role-manager .elementor-role-row .elementor-role-label span.elementor-role-toggle{text-align:left;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}#elementor-role-manager .elementor-role-row .elementor-role-label span.elementor-role-excluded-indicator{color:#a4afb7}#elementor-role-manager .elementor-role-row .elementor-role-controls{background-color:#f7f7f7;padding:20px 20px 5px}#elementor-role-manager .elementor-role-row .elementor-role-controls>div{margin-bottom:15px}#elementor-role-manager .elementor-role-row .elementor-role-controls .elementor-role-go-pro{display:-webkit-box;display:-ms-flexbox;display:flex}#elementor-role-manager .elementor-role-row .elementor-role-controls .elementor-role-go-pro .elementor-role-go-pro__desc{font-weight:500;font-style:italic}#elementor-role-manager .elementor-role-row .elementor-role-controls .elementor-role-go-pro .elementor-role-go-pro__link{text-align:left;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area{color:#c2cbd2;cursor:pointer}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area:hover .elementor-beta-tester-do-not-show-again,#elementor-beta-tester-modal .elementor-templates-modal__header__items-area:hover .elementor-templates-modal__header__item>i{color:#6d7882}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area .elementor-templates-modal__header__close{border:none}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area .elementor-beta-tester-do-not-show-again{text-transform:uppercase;font-weight:700;font-size:12px;-webkit-transition:all .3s;-o-transition:all .3s;transition:all .3s}#elementor-beta-tester-modal .dialog-lightbox-widget-content{max-width:500px;height:auto}#elementor-beta-tester-modal .dialog-lightbox-message{padding:40px;height:300px;background-color:#fff}#elementor-beta-tester-form__caption{font-weight:700;font-size:20px;color:#495157}#elementor-beta-tester-form__description{font-size:15px;color:#6d7882;margin-top:10px}#elementor-beta-tester-form__input-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;margin-top:30px}#elementor-beta-tester-form__input-wrapper .elementor-button{border-radius:3px 0 0 3px}#elementor-beta-tester-form__email{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;border:1px solid #d5dadf;border-left:0;border-radius:0 3px 3px 0;margin:0;padding:10px;height:50px}#elementor-beta-tester-form__terms{margin-top:40px;font-size:11px;color:#a4afb7}.e-experiment__title{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-ms-flex-wrap:wrap;flex-wrap:wrap}.e-experiment__title__indicator{position:absolute;height:10px;width:10px;border-radius:50%;border:2px solid #fff;-webkit-box-shadow:0 2px 4px rgba(0,0,0,.1);box-shadow:0 2px 4px rgba(0,0,0,.1);-ms-flex-negative:0;flex-shrink:0;margin-top:2px}.e-experiment__title__indicator--active{background:#39b54a}.e-experiment__title__label{margin-right:24px}.e-experiment__title__tag{background:#0085ba;color:#fff;font-size:.8em;padding:3px 6px;line-height:1;border-radius:3px;font-weight:600;margin-top:5px;margin-right:24px}.e-experiment__table-title{margin:30px 0}.e-experiment__status{font-size:.9em;font-weight:700;font-style:italic}.e-experiment__button.button{margin:18px 0 22px 14px}.e-experiment__dependency__title{color:#495157}.e-experiment__dependency__item{margin:0 1px;border:1px solid;padding:2px;font-size:13px;color:#6d7882;font-weight:700;background:#f1f3f5}#tab-experiments .form-table tr{border-bottom:1px solid #dcdcde}#tab-experiments .form-table tr:last-child{border-bottom:none}#tab-experiments .form-table tr .description{font-size:.9em;margin:10px 0;max-width:820px}.e-landing-pages-empty .elementor-blank_state{padding:5em 0 2em}.e-landing-pages-empty .e-trashed-items{text-align:center}
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
+ .elementor-button{font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-weight:500;text-transform:uppercase;outline:none;border:none;border-radius:3px;-webkit-transition-property:background,color,opacity,-webkit-box-shadow;transition-property:background,color,opacity,-webkit-box-shadow;-o-transition-property:background,color,box-shadow,opacity;transition-property:background,color,box-shadow,opacity;transition-property:background,color,box-shadow,opacity,-webkit-box-shadow;-webkit-transition-duration:.3s;-o-transition-duration:.3s;transition-duration:.3s}.elementor-button:hover{border:none}.elementor-button:not([disabled]){cursor:pointer}.elementor-button:not(.elementor-button-state) .elementor-state-icon{display:none}.elementor-button.elementor-button-success{color:#fff}.elementor-button.elementor-button-success[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-success:not([disabled]){background-color:#39b54a}.elementor-button.elementor-button-success:not([disabled]):hover{opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-success:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-brand{color:#fff}.elementor-button.elementor-button-brand[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-brand:not([disabled]){background-color:#93003c}.elementor-button.elementor-button-brand:not([disabled]):hover{opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-brand:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-warning{background-color:#a4afb7;color:#fff}.elementor-button.elementor-button-warning[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-warning:not([disabled]):hover{background-color:#b01b1b;opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-warning:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-danger{background-color:#d72b3f;color:#fff}.elementor-button.elementor-button-danger[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-danger:not([disabled]):hover{opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-danger:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-edit-template{display:inline-block;margin-top:15px;color:#fff}.elementor-button.elementor-button-default{background-color:#a4afb7;color:#fff;font-size:11px;padding:7px 21px}.elementor-button.elementor-button-default:hover{background-color:#6d7882;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-default:active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-default:visited{color:#fff}.elementor-button.elementor-button-go-pro{background-color:#93003c}.elementor-button i{margin-left:10px}#adminmenu #toplevel_page_elementor div.wp-menu-image:before{content:"\e813";font-family:eicons;font-size:18px;margin-top:1px}#adminmenu #toplevel_page_elementor a[href="admin.php?page=go_elementor_pro"]{font-weight:700}#adminmenu #toplevel_page_elementor a[href="admin.php?page=go_elementor_pro"]:hover{color:#e0005b}#adminmenu #toplevel_page_elementor .dashicons.dashicons-star-filled{height:auto}#adminmenu #menu-posts-elementor_library .wp-menu-image:before{content:"\e8ff";font-family:eicons;font-size:18px}#e-admin-menu__kit-library{color:#5cb85c}body.admin-color-fresh #adminmenu #toplevel_page_elementor a[href="admin.php?page=go_elementor_pro"]{color:#c60051}.elementor-plugins-gopro{color:#93003c;text-shadow:1px 1px 1px #eee;font-weight:700}#elementor-switch-mode{margin:15px 0}#elementor-editor-button,#elementor-switch-mode-button{outline:none;cursor:pointer}#elementor-editor-button i,#elementor-switch-mode-button i{margin-left:3px;font-size:125%;font-style:normal}body.elementor-editor-active .elementor-switch-mode-off{display:none}body.elementor-editor-active #elementor-switch-mode-button{background-color:#f7f7f7;color:#555;border-color:#ccc;-webkit-box-shadow:0 1px 0 #ccc!important;box-shadow:0 1px 0 #ccc!important;text-shadow:unset}body.elementor-editor-active #elementor-switch-mode-button:hover{background-color:#e9e9e9}body.elementor-editor-active #elementor-switch-mode-button:active{-webkit-box-shadow:inset 0 1px 0 #ccc;box-shadow:inset 0 1px 0 #ccc;-webkit-transform:translateY(1px);-ms-transform:translateY(1px);transform:translateY(1px)}body.elementor-editor-active #postdivrich{display:none!important}body.elementor-editor-active .block-editor-block-list__layout,body.elementor-editor-active .editor-block-list__layout,body.elementor-editor-inactive #elementor-editor,body.elementor-editor-inactive .elementor-switch-mode-on{display:none}body.elementor-editor-active .edit-post-layout__content .edit-post-visual-editor{-ms-flex-preferred-size:auto;flex-basis:auto}body.elementor-editor-active #elementor-editor{margin-bottom:50px}body.elementor-editor-active .edit-post-text-editor__body .editor-post-text-editor{display:none}body .block-editor #elementor-switch-mode{margin:0 15px}body .block-editor #elementor-switch-mode .button{margin:2px;height:33px;font-size:13px;line-height:1}body .block-editor #elementor-switch-mode .button i{padding-left:5px}.elementor-button{font-size:13px;text-decoration:none;padding:15px 40px}#elementor-editor{height:300px;width:100%;-webkit-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease}#elementor-editor .elementor-loader-wrapper{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:300px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#elementor-editor .elementor-loader{border-radius:50%;padding:40px;height:150px;width:150px;background-color:hsla(0,0%,100%,.9);-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:2px 2px 20px 4px rgba(0,0,0,.02);box-shadow:2px 2px 20px 4px rgba(0,0,0,.02)}#elementor-editor .elementor-loader-boxes{height:100%;width:100%;position:relative}#elementor-editor .elementor-loader-box{position:absolute;background-color:#d5dadf;-webkit-animation:load 1.8s linear infinite;animation:load 1.8s linear infinite}#elementor-editor .elementor-loader-box:first-of-type{width:20%;height:100%;left:0;top:0}#elementor-editor .elementor-loader-box:not(:first-of-type){right:0;height:20%;width:60%}#elementor-editor .elementor-loader-box:nth-of-type(2){top:0;-webkit-animation-delay:calc(1.8s / 4 * -1);animation-delay:calc(1.8s / 4 * -1)}#elementor-editor .elementor-loader-box:nth-of-type(3){top:40%;-webkit-animation-delay:calc(1.8s / 4 * -2);animation-delay:calc(1.8s / 4 * -2)}#elementor-editor .elementor-loader-box:nth-of-type(4){bottom:0;-webkit-animation-delay:calc(1.8s / 4 * -3);animation-delay:calc(1.8s / 4 * -3)}#elementor-editor .elementor-loading-title{color:#a4afb7;text-align:center;text-transform:uppercase;margin-top:30px;letter-spacing:7px;text-indent:7px;font-size:10px;width:100%}#elementor-go-to-edit-page-link{height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid #ddd;background-color:#f7f7f7;text-decoration:none;position:relative;font-family:Sans-serif}#elementor-go-to-edit-page-link:hover{background-color:#fff}#elementor-go-to-edit-page-link:focus{-webkit-box-shadow:none;box-shadow:none}#elementor-go-to-edit-page-link.elementor-animate #elementor-editor-button,#elementor-go-to-edit-page-link:not(.elementor-animate) .elementor-loader-wrapper{display:none}.elementor-button-spinner:before{font:normal 20px/.5 dashicons;speak:none;display:inline-block;padding:0;top:8px;right:-4px;position:relative;vertical-align:top;content:"\f463"}.elementor-button-spinner.loading:before{-webkit-animation:rotation 1s linear infinite;animation:rotation 1s linear infinite}.elementor-button-spinner.success:before{content:"\f147";color:#46b450}.elementor-blank_state{padding:5em 0;margin:auto;max-width:520px;text-align:center;color:#6d7882;font-family:Roboto,sans-serif}.elementor-blank_state i{font-size:50px;color:#a4afb7}.elementor-blank_state h3{font-size:32px;font-weight:300;color:inherit;margin:40px 0 10px;line-height:1.2}.elementor-blank_state p{font-size:16px;font-weight:400;color:#a4afb7;margin-bottom:40px}.elementor-blank_state .elementor-button{display:inline-block}#available-widgets [class*=elementor-template] .widget-title:before{content:"\e813";font-family:eicons;font-size:17px}.elementor-settings-form-page{padding-top:30px}._elementor_settings_update_time,.elementor-settings-form-page:not(.elementor-active){display:none}#confirm_fa_migration_admin_modal .dialog-confirm-ok{color:#6d7882}body.post-type-attachment table.media .column-title .media-icon img[src$=".svg"]{width:100%}.e-major-update-warning{margin-bottom:5px;max-width:1000px;display:-webkit-box;display:-ms-flexbox;display:flex}.e-major-update-warning__separator{margin:15px -12px}.e-major-update-warning__icon{font-size:17px;margin-left:9px;margin-right:2px}.e-major-update-warning__title{font-weight:600;margin-bottom:10px}.e-major-update-warning+p{display:none}.notice-success .e-major-update-warning__separator{border:1px solid #46b450}.notice-success .e-major-update-warning__icon{color:#79ba49}.notice-warning .e-major-update-warning__separator{border:1px solid #ffb900}.notice-warning .e-major-update-warning__icon{color:#f56e28}.plugins table.e-compatibility-update-table tr{background:transparent}.plugins table.e-compatibility-update-table tr th{font-weight:600}.plugins table.e-compatibility-update-table tr td,.plugins table.e-compatibility-update-table tr th{min-width:250px;font-size:13px;background:transparent;-webkit-box-shadow:none;box-shadow:none;border:none;padding:5px 0 5px 15px}:root{--e-focus-color:rgba(0,115,170,0.4);--e-context-primary-color:#0073aa;--e-context-primary-color-dark:#005177;--e-context-primary-tint-4:rgba(0,115,170,0.4);--e-context-primary-tint-1:rgba(0,115,170,0.04);--e-context-success-color:#39b54a;--e-context-success-color-dark:#2d8e3a;--e-context-success-tint-4:rgba(57,181,74,0.4);--e-context-success-tint-1:rgba(57,181,74,0.04);--e-context-info-color:#71d7f7;--e-context-info-color-dark:#41c9f4;--e-context-info-tint-4:rgba(113,215,247,0.4);--e-context-info-tint-1:rgba(113,215,247,0.04);--e-context-warning-color:#fcb92c;--e-context-warning-color-dark:#f2a503;--e-context-warning-tint-4:rgba(252,185,44,0.4);--e-context-warning-tint-1:rgba(252,185,44,0.04);--e-context-error-color:#d72b3f;--e-context-error-color-dark:#ae2131;--e-context-error-tint-4:rgba(215,43,63,0.4);--e-context-error-tint-1:rgba(215,43,63,0.04);--e-context-cta-color:#93003c;--e-context-cta-color-dark:#600027;--e-context-cta-tint-4:rgba(147,0,60,0.4);--e-context-cta-tint-1:rgba(147,0,60,0.04)}.e-getting-started{max-width:900px;padding:2.5em 0;margin:auto;text-align:center}.e-getting-started__header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-shadow:0 0 8px rgba(0,0,0,.1);box-shadow:0 0 8px rgba(0,0,0,.1)}.e-getting-started__header .e-logo-wrapper{font-size:10px;margin-left:10px}.e-getting-started__title{padding:0 15px;font-weight:600;text-transform:uppercase;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.e-getting-started__skip{border-right:1px solid #eee;font-size:16px;color:inherit}.e-getting-started__skip i{padding:15px}.e-getting-started__content{padding:50px}.e-getting-started__content h2{font-size:2em;margin-top:0}.e-getting-started__content--narrow{max-width:500px;margin:auto}.e-getting-started__video{margin:40px 0 60px}.e-getting-started__video iframe{-webkit-box-shadow:10px 10px 20px rgba(0,0,0,.15);box-shadow:10px 10px 20px rgba(0,0,0,.15)}.e-getting-started__actions .button-primary{margin-left:20px}:root{--e-button-padding-y:0.4375rem;--e-button-padding-x:0.75rem;--e-button-font-size:0.8125rem;--e-button-font-weight:500;--e-button-line-height:0.9375rem;--e-button-border-radius:3px;--e-button-context-color:var(--e-context-primary-color);--e-button-context-color-dark:var(--e-context-primary-color-dark);--e-button-context-tint:var(--e-context-primary-tint-1)}.e-button{display:inline-block;font-weight:var(--e-button-font-weight);text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:#fff;border:0;text-decoration:none;background:var(--e-button-context-color);padding:var(--e-button-padding-y) var(--e-button-padding-x);font-size:var(--e-button-font-size);line-height:var(--e-button-line-height);border-radius:var(--e-button-border-radius);-webkit-transition:background-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;-o-transition:background-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out}.e-button:active,.e-button:focus,.e-button:hover{color:#fff;text-decoration:none;background:var(--e-button-context-color-dark)}.e-button.focus,.e-button:focus{outline:0;-webkit-box-shadow:0 0 0 2px var(--e-focus-color);box-shadow:0 0 0 2px var(--e-focus-color)}.e-button.disabled,.e-button:disabled{opacity:.5;-webkit-box-shadow:none;box-shadow:none}.e-button:not(:disabled):not(.disabled){cursor:pointer}.e-button:not(:disabled):not(.disabled).active:focus,.e-button:not(:disabled):not(.disabled):active:focus{-webkit-box-shadow:0 0 0 2px var(--e-focus-color);box-shadow:0 0 0 2px var(--e-focus-color)}.e-button--primary{--e-button-context-color:var(--e-context-primary-color);--e-button-context-color-dark:var(--e-context-primary-color-dark);--e-button-context-tint:var(--e-context-primary-tint-1);--e-focus-color:var(--e-context-primary-tint-4)}.e-button--success{--e-button-context-color:var(--e-context-success-color);--e-button-context-color-dark:var(--e-context-success-color-dark);--e-button-context-tint:var(--e-context-success-tint-1);--e-focus-color:var(--e-context-success-tint-4)}.e-button--info{--e-button-context-color:var(--e-context-info-color);--e-button-context-color-dark:var(--e-context-info-color-dark);--e-button-context-tint:var(--e-context-info-tint-1);--e-focus-color:var(--e-context-info-tint-4)}.e-button--warning{--e-button-context-color:var(--e-context-warning-color);--e-button-context-color-dark:var(--e-context-warning-color-dark);--e-button-context-tint:var(--e-context-warning-tint-1);--e-focus-color:var(--e-context-warning-tint-4)}.e-button--error{--e-button-context-color:var(--e-context-error-color);--e-button-context-color-dark:var(--e-context-error-color-dark);--e-button-context-tint:var(--e-context-error-tint-1);--e-focus-color:var(--e-context-error-tint-4)}.e-button--cta{--e-button-context-color:var(--e-context-cta-color);--e-button-context-color-dark:var(--e-context-cta-color-dark);--e-button-context-tint:var(--e-context-cta-tint-1);--e-focus-color:var(--e-context-cta-tint-4)}.e-button.e-button--outline{color:var(--e-button-context-color);background:none;border:1px solid}.e-button.e-button--outline:focus,.e-button.e-button--outline:hover{color:var(--e-button-context-color-dark);background:var(--e-button-context-tint)}.e-button.e-button--outline.disabled,.e-button.e-button--outline:disabled{color:var(--e-button-context-color-dark);background:#818a91}.e-button>i{line-height:inherit;height:var(--e-button-line-height);width:-webkit-min-content;width:-moz-min-content;width:min-content}.e-button>*+*{-webkit-margin-start:.5ch;margin-inline-start:.5ch}.e-button--link{color:var(--e-button-context-color);background-color:transparent}.e-button--link:focus,.e-button--link:hover{color:var(--e-button-context-color-dark);background:var(--e-button-context-tint)}.e-button--link.disabled,.e-button--link:disabled{color:#818a91}a.e-button.disabled,fieldset:disabled a.e-button{pointer-events:none}:root{--e-notice-bg:#fff;--e-notice-border-color:#ccd0d4;--e-notice-context-color:#93003c;--e-notice-context-tint:var(--e-context-cta-tint-1);--e-notice-box-shadow:0 1px 4px rgba(0,0,0,0.15);--e-notice-dismiss-color:#6d7882}.e-notice{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;background:var(--e-notice-bg);border:1px solid var(--e-notice-border-color);border-inline-start-width:4px;-webkit-box-shadow:var(--e-notice-box-shadow);box-shadow:var(--e-notice-box-shadow);margin:5px 20px 5px 2px}.e-notice.notice{padding:0}.e-notice:before{display:block;content:"";position:absolute;right:-4px;top:-1px;bottom:-1px;width:4px;background-color:var(--e-notice-context-color)}.e-notice--primary{--e-notice-context-color:var(--e-context-primary-color);--e-notice-context-color-dark:var(--e-context-primary-color-dark);--e-notice-context-tint:var(--e-context-primary-tint-1)}.e-notice--success{--e-notice-context-color:var(--e-context-success-color);--e-notice-context-color-dark:var(--e-context-success-color-dark);--e-notice-context-tint:var(--e-context-success-tint-1)}.e-notice--info{--e-notice-context-color:var(--e-context-info-color);--e-notice-context-color-dark:var(--e-context-info-color-dark);--e-notice-context-tint:var(--e-context-info-tint-1)}.e-notice--warning{--e-notice-context-color:var(--e-context-warning-color);--e-notice-context-color-dark:var(--e-context-warning-color-dark);--e-notice-context-tint:var(--e-context-warning-tint-1)}.e-notice--error{--e-notice-context-color:var(--e-context-error-color);--e-notice-context-color-dark:var(--e-context-error-color-dark);--e-notice-context-tint:var(--e-context-error-tint-1)}.e-notice--cta{--e-notice-context-color:var(--e-context-cta-color);--e-notice-context-color-dark:var(--e-context-cta-color-dark);--e-notice-context-tint:var(--e-context-cta-tint-1)}.e-notice--extended{--e-notice-is-extended:1}.e-notice--dismissible{padding-right:38px}.e-notice__aside{overflow:hidden;background-color:var(--e-notice-context-tint);width:calc(var(--e-notice-is-extended, 0) * 50px);text-align:center;padding-top:15px;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.e-notice__icon-wrapper{display:inline-block;font-size:.625rem;max-height:1.5rem;width:1.5rem;line-height:1.5rem;border-radius:100px;background:var(--e-notice-context-color);color:#fff;text-shadow:0 0 3px var(--e-notice-context-color-dark),0 0 1px var(--e-notice-context-color-dark),0 0 1px var(--e-notice-context-color-dark)}.e-notice__content{padding:20px}.e-notice__actions{display:-webkit-box;display:-ms-flexbox;display:flex}.e-notice__actions>*+*{-webkit-margin-start:8px;margin-inline-start:8px}.e-notice__dismiss{width:20px;height:20px;line-height:20px;font-size:.8125rem;text-align:center;background:none;display:block;position:absolute;top:0;inset-inline-end:1px;border:none;margin:0;padding:9px;cursor:pointer;font-style:normal}.e-notice__dismiss:before{font-family:eicons;display:inline-block;content:"\e87f";color:var(--e-notice-dismiss-color);width:20px;border-radius:20px;speak:none;text-align:center;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.e-notice__dismiss:active:before,.e-notice__dismiss:focus:before,.e-notice__dismiss:hover:before{font-weight:700}.e-notice__dismiss:focus:before{color:#fff;background:var(--e-notice-dismiss-color);outline:none}.e-notice__dismiss:focus{outline:none}.e-notice p{line-height:1.2;padding:0;margin:0}.e-notice p+.e-notice__actions{margin-top:1rem}.e-notice h3{font-size:1.0625rem;line-height:1.2;margin:0}.e-notice h3+p{margin-top:8px}.elementor-admin-alert{padding:15px;border-left:5px solid transparent;position:relative;font-size:12px;line-height:1.5;text-align:right}.elementor-admin-alert a{color:inherit}.elementor-admin-alert.elementor-alert-info{color:#31708f;background-color:#d9edf7;border-color:#bcdff1}.elementor-admin-alert.elementor-alert-success{color:#3c763d;background-color:#dff0d8;border-color:#cae6be}.elementor-admin-alert.elementor-alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#f9f0c3}.elementor-admin-alert.elementor-alert-danger{color:#a94442;background-color:#f2dede;border-color:#e8c4c4}#elementor-system-info{padding:15px}#elementor-system-info .elementor-system-info-section{margin-bottom:10px}#elementor-system-info .elementor-system-info-section>.elementor-system-info-report-name{padding-left:10px;border-bottom:1px solid #e1e1e1}#elementor-system-info .elementor-system-info-section .widefat{white-space:pre}#elementor-system-info .elementor-system-info-section .elementor-log-entries{white-space:pre-wrap}#elementor-system-info .elementor-system-info-section:not(.elementor-system-info-log) tbody td:first-child{width:300px}#elementor-system-info .elementor-system-info-report-name{text-transform:uppercase;font-size:14px;margin:0;line-height:2}#elementor-system-info .elementor-system-info-report-row{overflow:hidden;padding:5px 0}#elementor-system-info .elementor-system-info-report-row>*{float:left}#elementor-system-info .elementor-system-info-field-recommendation,#elementor-system-info .elementor-system-info-report-field{padding-left:10px;color:#7f7f7f}#elementor-system-info .elementor-system-info-report-fields{padding-left:20px}#elementor-system-info .elementor-system-info-plugin-name{color:#000}#elementor-system-info .elementor-system-info-plugin-properties{padding:10px}#elementor-system-info #elementor-system-info-raw-code{width:100%;height:200px}#elementor-system-info #elementor-system-info-raw-code-label{padding:5px;display:block}#elementor-system-info .elementor-warning td:first-child{border-right:3px solid #fcb92c}#elementor-system-info a.box-title-tool{font-size:80%;margin-right:15px;color:#818a91}#elementor-system-info a.box-title-tool:hover{text-decoration:underline}#elementor-system-info #elementor-usage-recalc{font-size:12px;color:#fff;background-color:#a4afb7;padding:4px 18px 5px;border-radius:3px}@-webkit-keyframes elementor-rotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes elementor-rotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}#elementor-deactivate-feedback-dialog-wrapper{display:none}#elementor-deactivate-feedback-modal .dialog-widget-content{width:550px}#elementor-deactivate-feedback-modal .dialog-header{padding:18px 15px;-webkit-box-shadow:0 0 8px rgba(0,0,0,.1);box-shadow:0 0 8px rgba(0,0,0,.1);text-align:right}#elementor-deactivate-feedback-modal .dialog-message{padding:30px 30px 0;text-align:right}#elementor-deactivate-feedback-modal .elementor-deactivate-feedback-dialog-input{float:right;margin:0 0 0 15px;-webkit-box-shadow:none;box-shadow:none}#elementor-deactivate-feedback-modal .elementor-deactivate-feedback-dialog-input:not(:checked)~.elementor-feedback-text{display:none}#elementor-deactivate-feedback-modal .elementor-deactivate-feedback-dialog-label{display:block;font-size:13px;color:#6d7882}#elementor-deactivate-feedback-modal .elementor-feedback-text{margin:10px 30px 0 0;padding:5px;font-size:13px;-webkit-box-shadow:none;box-shadow:none;background-color:#fff;width:92%}#elementor-deactivate-feedback-modal .dialog-buttons-wrapper{border-top:none;text-align:right;padding:20px 30px 30px;overflow:hidden}#elementor-deactivate-feedback-modal .dialog-submit{background-color:#93003c;border-radius:3px;color:#fff;line-height:1;padding:12px 20px;font-size:13px;width:180px;height:38px}#elementor-deactivate-feedback-modal .dialog-submit.elementor-loading:before{display:inline-block;content:"\f463";font:18px dashicons;-webkit-animation:elementor-rotation 2s linear infinite;animation:elementor-rotation 2s linear infinite}#elementor-deactivate-feedback-modal .dialog-skip{font-size:12px;color:#a4afb7;background:none;float:left;width:auto}#elementor-deactivate-feedback-modal[data-feedback-selected=elementor_pro] .elementor-feedback-text{color:#b01b1b;padding:0}#elementor-deactivate-feedback-modal[data-feedback-selected=elementor_pro] .dialog-submit{display:none}#elementor-deactivate-feedback-dialog-header i{color:#93003c;font-size:19px}#elementor-deactivate-feedback-dialog-header-title{font-size:15px;text-transform:uppercase;font-weight:700;padding-right:5px}#elementor-deactivate-feedback-dialog-form-caption{font-weight:700;font-size:15px;color:#495157;line-height:1.4}#elementor-deactivate-feedback-dialog-form-body{padding-top:30px}.elementor-deactivate-feedback-dialog-input-wrapper{line-height:1;overflow:hidden;margin-bottom:15px}#elementor-hidden-area{display:none}#elementor-import-template-trigger{cursor:pointer}#elementor-import-template-area{display:none;margin:50px 0 30px;text-align:center}#elementor-import-template-form{display:inline-block;margin-top:30px;padding:30px 50px;background-color:#fff;border:1px solid #e5e5e5}#elementor-import-template-title{font-size:18px;color:#555d66}.form-table:not(.elementor-maintenance-mode-is-enabled) .elementor-default-hide{display:none}.elementor-maintenance-mode-error{color:red;line-height:1.6;display:none}#tab-fontawesome4_migration.elementor-active~p.submit,#tab-import-export-kit.elementor-active~p.submit,#tab-replace_url.elementor-active~p.submit{display:none}#elementor_replace_url>div{max-width:800px}#elementor_replace_url>div input{margin-bottom:6px}#elementor_rollback>div,#elementor_rollback_pro>div{display:-webkit-box;display:-ms-flexbox;display:flex}#elementor_rollback>div input,#elementor_rollback>div select,#elementor_rollback_pro>div input,#elementor_rollback_pro>div select{margin-left:6px}.tab-import-export-kit__wrapper{margin:40px 0;max-width:700px}.tab-import-export-kit__container{background-color:#fff;font-size:16px;max-width:700px;padding:30px}.tab-import-export-kit__container:not(:first-child){margin-top:5px}.tab-import-export-kit__container p{color:#a4afb7;font-size:16px;margin:20px 0 25px}.tab-import-export-kit__info{font-size:14px}.tab-import-export-kit__container a:not(.elementor-button),.tab-import-export-kit__info a{color:#58d0f5;text-decoration:underline}.tab-import-export-kit__box{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.tab-import-export-kit__box h2{color:#6d7882;font-size:28px;font-weight:400;line-height:1;margin:0}.tab-import-export-kit__box .elementor-button.elementor-button-success{font-weight:700;padding:8px 16px;text-transform:none}#dashboard-widgets .e-dashboard-widget h3.e-heading{font-weight:600;margin-bottom:13px}#dashboard-widgets .e-dashboard-widget .e-divider_bottom{border-bottom:1px solid #eee;margin:0 -12px;padding:6px 12px}#dashboard-widgets .e-dashboard-widget .e-divider_top{border-top:1px solid #eee;margin:0 -12px;padding:6px 12px}#dashboard-widgets .e-dashboard-widget .e-news-feed-wrap .e-divider_top,#dashboard-widgets .e-dashboard-widget .e-quick-actions-wrap .e-divider_top{padding-top:18px;margin-top:18px}.e-dashboard-widget .dashicons{color:#606a73}.e-dashboard-widget ul.e-action-list li{margin-top:14px}.e-dashboard-widget ul.e-action-list li a{margin-right:5px}#e-dashboard-overview .dashicons{vertical-align:middle;font-size:17px}#e-dashboard-overview .e-overview__header{display:table;width:100%;-webkit-box-shadow:0 5px 8px rgba(0,0,0,.05);box-shadow:0 5px 8px rgba(0,0,0,.05);margin:0 -12px 8px;padding:0 12px 12px}#e-dashboard-overview .e-overview__create,#e-dashboard-overview .e-overview__logo,#e-dashboard-overview .e-overview__versions{display:table-cell;vertical-align:middle}#e-dashboard-overview .e-overview__logo{width:30px}#e-dashboard-overview .e-overview__versions{padding:0 10px;font-size:.9em;line-height:1.5}#e-dashboard-overview .e-overview__version{display:block}#e-dashboard-overview .e-overview__create{text-align:left}#e-dashboard-overview .e-overview__feed{font-size:14px;font-weight:500}#e-dashboard-overview .e-overview__post{margin-top:10px}#e-dashboard-overview .e-overview__post-link{display:inline-block}#e-dashboard-overview .e-overview__badge{background:#39b54a;color:#fff;font-size:.75em;padding:3px 6px;border-radius:3px;text-transform:uppercase}#e-dashboard-overview .e-overview__post-description{margin:0 0 1.5em}#e-dashboard-overview .e-overview__recently-edited li{color:#72777c}#e-dashboard-overview .e-overview__footer.e-divider_top{padding-top:12px;padding-bottom:0}#e-dashboard-overview .e-overview__footer ul{display:-webkit-box;display:-ms-flexbox;display:flex;list-style:none;margin:0;padding:0}#e-dashboard-overview .e-overview__footer ul li{padding:0 10px;margin:0;border-right:1px solid #ddd}#e-dashboard-overview .e-overview__footer ul li:first-child{padding-right:0;border:none}#e-dashboard-overview .e-overview__go-pro a{color:#93003c;font-weight:500}.post-type-elementor_library #elementor-template-library-tabs-wrapper{padding-top:2em;margin-bottom:2em}.post-type-elementor_library th#taxonomy-elementor_library_category{width:110px}#elementor-new-template-modal .dialog-message{max-height:70vh}#elementor-new-template-modal .e-hidden{display:none!important}#elementor-new-template-dialog-content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:100%;text-align:right;color:#6d7882}@media (max-width:1439px){#elementor-new-template-dialog-content{padding:0 50px}}@media (min-width:1440px){#elementor-new-template-dialog-content{padding:0 120px}}#elementor-new-template__description{width:35%;max-width:300px;padding-left:100px}#elementor-new-template__description__title{font-size:30px;color:#556068}#elementor-new-template__description__title span{font-weight:700}#elementor-new-template__description__content{font-size:16px;padding:30px 0}#elementor-new-template__take_a_tour{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:15px}#elementor-new-template__take_a_tour i{color:#93003c;font-size:30px}#elementor-new-template__take_a_tour a{color:#6d7882;padding-right:10px;text-decoration:none;font-weight:500}#elementor-new-template__form{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:440px;padding:55px;background-color:#fff;border-radius:3px;-webkit-box-shadow:0 2px 30px 0 rgba(0,0,0,.08);box-shadow:0 2px 30px 0 rgba(0,0,0,.08)}#elementor-new-template__form__title{font-size:23px;color:#556068}#elementor-new-template__form__template-type.elementor-form-field__select{max-width:none}#elementor-new-template__form__template-type-badge{position:absolute;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:2px;border-radius:2px;background-color:#f1f3f5;padding:4px;font-size:8px;font-weight:500;line-height:1;text-transform:uppercase;top:50%;inset-inline-end:28px;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}#elementor-new-template__form .elementor-form-field__label{display:block;margin:25px 0 7px;font-size:14px;line-height:1}#elementor-new-template__form .elementor-form-field input,#elementor-new-template__form .elementor-form-field select{width:100%;height:50px;padding:10px;font-size:14px;-webkit-box-shadow:none;box-shadow:none;border-radius:3px;background:none;color:#495157;border:1px solid;outline:none}#elementor-new-template__form .elementor-form-field input:not(:focus),#elementor-new-template__form .elementor-form-field select:not(:focus){border-color:#d5dadf}#elementor-new-template__form .elementor-form-field input:focus,#elementor-new-template__form .elementor-form-field select:focus{border-color:#a4afb7}#elementor-new-template__form .elementor-form-field__select{appearance:none;-webkit-appearance:none;-moz-appearance:none;cursor:pointer}#elementor-new-template__form .elementor-form-field__select__wrapper{position:relative}#elementor-new-template__form .elementor-form-field__select__wrapper:after{font-family:eicons;content:"\e8ad";position:absolute;top:50%;left:10px;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}#elementor-new-template__form__lock_button,#elementor-new-template__form__submit{display:block;width:100%;height:50px;margin-top:24px;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;text-transform:none}@media (max-width:1024px){#elementor-new-template__description{max-width:250px;padding-left:30px}}@media (max-width:767px){#elementor-new-template__description{display:none}}#elementor-role-manager{max-width:500px;margin-top:50px}#elementor-role-manager h3{color:#6d7882;font-weight:400;font-size:22px}#elementor-role-manager .elementor-settings-form-page{padding:0}#elementor-role-manager .elementor-role-row{background:#fff;color:#6d7882;margin-bottom:2px}#elementor-role-manager .elementor-role-row .elementor-role-label{display:-webkit-box;display:-ms-flexbox;display:flex;padding:15px 20px;font-weight:500;cursor:pointer}#elementor-role-manager .elementor-role-row .elementor-role-label span.elementor-role-name{padding-left:20px}#elementor-role-manager .elementor-role-row .elementor-role-label span.elementor-role-toggle{text-align:left;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}#elementor-role-manager .elementor-role-row .elementor-role-label span.elementor-role-excluded-indicator{color:#a4afb7}#elementor-role-manager .elementor-role-row .elementor-role-controls{background-color:#f7f7f7;padding:20px 20px 5px}#elementor-role-manager .elementor-role-row .elementor-role-controls>div{margin-bottom:15px}#elementor-role-manager .elementor-role-row .elementor-role-controls .elementor-role-go-pro{display:-webkit-box;display:-ms-flexbox;display:flex}#elementor-role-manager .elementor-role-row .elementor-role-controls .elementor-role-go-pro .elementor-role-go-pro__desc{font-weight:500;font-style:italic}#elementor-role-manager .elementor-role-row .elementor-role-controls .elementor-role-go-pro .elementor-role-go-pro__link{text-align:left;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area{color:#c2cbd2;cursor:pointer}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area:hover .elementor-beta-tester-do-not-show-again,#elementor-beta-tester-modal .elementor-templates-modal__header__items-area:hover .elementor-templates-modal__header__item>i{color:#6d7882}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area .elementor-templates-modal__header__close{border:none}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area .elementor-beta-tester-do-not-show-again{text-transform:uppercase;font-weight:700;font-size:12px;-webkit-transition:all .3s;-o-transition:all .3s;transition:all .3s}#elementor-beta-tester-modal .dialog-lightbox-widget-content{max-width:500px;height:auto}#elementor-beta-tester-modal .dialog-lightbox-message{padding:40px;height:300px;background-color:#fff}#elementor-beta-tester-form__caption{font-weight:700;font-size:20px;color:#495157}#elementor-beta-tester-form__description{font-size:15px;color:#6d7882;margin-top:10px}#elementor-beta-tester-form__input-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;margin-top:30px}#elementor-beta-tester-form__input-wrapper .elementor-button{border-radius:3px 0 0 3px}#elementor-beta-tester-form__email{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;border:1px solid #d5dadf;border-left:0;border-radius:0 3px 3px 0;margin:0;padding:10px;height:50px}#elementor-beta-tester-form__terms{margin-top:40px;font-size:11px;color:#a4afb7}.e-experiment__title{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.e-experiment__title__indicator{position:absolute;height:10px;width:10px;border-radius:50%;border:2px solid #fff;-webkit-box-shadow:0 2px 4px rgba(0,0,0,.1);box-shadow:0 2px 4px rgba(0,0,0,.1);-ms-flex-negative:0;flex-shrink:0;margin-top:2px}.e-experiment__title__indicator--active{background:#39b54a}.e-experiment__title__label{margin-right:24px}.e-experiment__title__tag{background:#0085ba;color:#fff;font-size:.8em;padding:3px 6px;line-height:1;border-radius:3px;font-weight:600;margin-top:5px;margin-right:24px}.e-experiment__table-title{margin:30px 0}.e-experiment__dependency,.e-experiment__status{margin-top:4px;font-size:.9em;line-height:18px;font-weight:700;font-style:italic}.e-experiment__button.button{margin:18px 0 22px 14px}.e-experiment__dependency{color:#21759b}.e-experiment__dependency__title{font-weight:inherit}#tab-experiments .form-table tr{border-bottom:1px solid #dcdcde}#tab-experiments .form-table tr:last-child{border-bottom:none}#tab-experiments .form-table tr .description{font-size:.9em;margin:10px 0;max-width:820px}.e-landing-pages-empty .elementor-blank_state{padding:5em 0 2em}.e-landing-pages-empty .e-trashed-items{text-align:center}
assets/css/admin-top-bar-rtl.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
  #e-dashboard-widget-admin-top-bar {
3
  position: absolute;
4
  opacity: 0;
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
  #e-dashboard-widget-admin-top-bar {
3
  position: absolute;
4
  opacity: 0;
assets/css/admin-top-bar-rtl.min.css CHANGED
@@ -1,2 +1,2 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
  #e-dashboard-widget-admin-top-bar{position:absolute;opacity:0;pointer-events:none}#e-admin-top-bar-root{left:0;background:#fff;-webkit-box-shadow:0 4px 6px rgba(0,0,0,.0394871);box-shadow:0 4px 6px rgba(0,0,0,.0394871);display:none;position:absolute;top:0;width:calc(100% - 160px);z-index:1}#e-admin-top-bar-root .e-admin-top-bar{display:-webkit-box;display:-ms-flexbox;display:flex;height:50px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:0 16px}#e-admin-top-bar-root .page-title-action{background:#f7f7f7;border:1px solid #0275d8;border-radius:3px;-webkit-box-sizing:border-box;box-sizing:border-box;color:#0275d8;cursor:pointer;display:inline-block;line-height:30px;margin:0 4px;min-height:30px;padding:0 10px;text-decoration:none}#e-admin-top-bar-root .page-title-action:hover{background:#eceeef;border-color:#1b607f;color:#1b607f}#e-admin-top-bar-root .e-admin-top-bar__heading{-webkit-margin-end:40px;margin-inline-end:40px}#e-admin-top-bar-root .e-admin-top-bar__heading,#e-admin-top-bar-root .e-admin-top-bar__main-area{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__main-area button{margin:0 4px}#e-admin-top-bar-root .e-admin-top-bar__secondary-area{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__heading-logo{width:30px;background:#93003c;border-radius:15px;color:#fff;display:inline-block;font-size:.875rem;height:1.875rem;line-height:1.875rem;text-align:center;width:1.875rem}#e-admin-top-bar-root .e-admin-top-bar__heading-title{color:#495157;font-size:15px;font-weight:700;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;padding:0 8px;line-height:normal;text-transform:uppercase}#e-admin-top-bar-root.e-admin-top-bar--active{display:block}#e-admin-top-bar-root.e-admin-top-bar--active~#wpbody #wpbody-content{margin-top:50px}#e-admin-top-bar-root.e-admin-top-bar--active~#wpbody .wrap{clear:both;padding-top:10px}#e-admin-top-bar-root.e-admin-top-bar--active~#wpbody .wrap h1{display:none}#e-admin-top-bar-root:not(.e-admin-top-bar--active)~#wpbody .wrap .page-title-action,#e-admin-top-bar-root:not(.e-admin-top-bar--active)~#wpbody .wrap h1{display:inline-block}#e-admin-top-bar-root .e-admin-top-bar__bar-button{-webkit-box-align:center;-ms-flex-align:center;align-items:center;cursor:pointer;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin:0 10px;text-decoration:none;color:#6d7882}#e-admin-top-bar-root .e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-title{color:#6d7882}#e-admin-top-bar-root .e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-icon{margin:0 4px}#e-admin-top-bar-root .e-admin-top-bar__bar-button:hover .e-admin-top-bar__bar-button-icon,#e-admin-top-bar-root .e-admin-top-bar__bar-button:hover .e-admin-top-bar__bar-button-title{color:#93003c}#e-admin-top-bar-root .e-admin-top-bar__bar-button-title{font-size:13px;font-weight:500;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;margin:0 4px;line-height:normal}#e-admin-top-bar-root~#wpbody .wrap .page-title-action,#e-admin-top-bar-root~#wpbody .wrap h1{display:none}@media screen and (max-width:960px){#e-admin-top-bar-root{width:calc(100% - 36px)}}@media screen and (max-width:782px){#e-admin-top-bar-root{width:100%}}@media screen and (max-width:600px){#e-admin-top-bar-root{top:46px}}@media (max-width:768px){#e-admin-top-bar-root{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__main-area-buttons{position:absolute;top:calc(100% + 10px)}#e-admin-top-bar-root .e-admin-top-bar__secondary-area .e-admin-top-bar__secondary-area-buttons,#e-admin-top-bar-root .e-admin-top-bar__secondary-area>.e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-title{display:none}}@media (min-width:768px){#e-admin-top-bar-root .e-admin-top-bar__secondary-area .e-admin-top-bar__secondary-area-buttons{display:-webkit-box;display:-ms-flexbox;display:flex}}
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
  #e-dashboard-widget-admin-top-bar{position:absolute;opacity:0;pointer-events:none}#e-admin-top-bar-root{left:0;background:#fff;-webkit-box-shadow:0 4px 6px rgba(0,0,0,.0394871);box-shadow:0 4px 6px rgba(0,0,0,.0394871);display:none;position:absolute;top:0;width:calc(100% - 160px);z-index:1}#e-admin-top-bar-root .e-admin-top-bar{display:-webkit-box;display:-ms-flexbox;display:flex;height:50px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:0 16px}#e-admin-top-bar-root .page-title-action{background:#f7f7f7;border:1px solid #0275d8;border-radius:3px;-webkit-box-sizing:border-box;box-sizing:border-box;color:#0275d8;cursor:pointer;display:inline-block;line-height:30px;margin:0 4px;min-height:30px;padding:0 10px;text-decoration:none}#e-admin-top-bar-root .page-title-action:hover{background:#eceeef;border-color:#1b607f;color:#1b607f}#e-admin-top-bar-root .e-admin-top-bar__heading{-webkit-margin-end:40px;margin-inline-end:40px}#e-admin-top-bar-root .e-admin-top-bar__heading,#e-admin-top-bar-root .e-admin-top-bar__main-area{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__main-area button{margin:0 4px}#e-admin-top-bar-root .e-admin-top-bar__secondary-area{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__heading-logo{width:30px;background:#93003c;border-radius:15px;color:#fff;display:inline-block;font-size:.875rem;height:1.875rem;line-height:1.875rem;text-align:center;width:1.875rem}#e-admin-top-bar-root .e-admin-top-bar__heading-title{color:#495157;font-size:15px;font-weight:700;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;padding:0 8px;line-height:normal;text-transform:uppercase}#e-admin-top-bar-root.e-admin-top-bar--active{display:block}#e-admin-top-bar-root.e-admin-top-bar--active~#wpbody #wpbody-content{margin-top:50px}#e-admin-top-bar-root.e-admin-top-bar--active~#wpbody .wrap{clear:both;padding-top:10px}#e-admin-top-bar-root.e-admin-top-bar--active~#wpbody .wrap h1{display:none}#e-admin-top-bar-root:not(.e-admin-top-bar--active)~#wpbody .wrap .page-title-action,#e-admin-top-bar-root:not(.e-admin-top-bar--active)~#wpbody .wrap h1{display:inline-block}#e-admin-top-bar-root .e-admin-top-bar__bar-button{-webkit-box-align:center;-ms-flex-align:center;align-items:center;cursor:pointer;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin:0 10px;text-decoration:none;color:#6d7882}#e-admin-top-bar-root .e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-title{color:#6d7882}#e-admin-top-bar-root .e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-icon{margin:0 4px}#e-admin-top-bar-root .e-admin-top-bar__bar-button:hover .e-admin-top-bar__bar-button-icon,#e-admin-top-bar-root .e-admin-top-bar__bar-button:hover .e-admin-top-bar__bar-button-title{color:#93003c}#e-admin-top-bar-root .e-admin-top-bar__bar-button-title{font-size:13px;font-weight:500;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;margin:0 4px;line-height:normal}#e-admin-top-bar-root~#wpbody .wrap .page-title-action,#e-admin-top-bar-root~#wpbody .wrap h1{display:none}@media screen and (max-width:960px){#e-admin-top-bar-root{width:calc(100% - 36px)}}@media screen and (max-width:782px){#e-admin-top-bar-root{width:100%}}@media screen and (max-width:600px){#e-admin-top-bar-root{top:46px}}@media (max-width:768px){#e-admin-top-bar-root{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__main-area-buttons{position:absolute;top:calc(100% + 10px)}#e-admin-top-bar-root .e-admin-top-bar__secondary-area .e-admin-top-bar__secondary-area-buttons,#e-admin-top-bar-root .e-admin-top-bar__secondary-area>.e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-title{display:none}}@media (min-width:768px){#e-admin-top-bar-root .e-admin-top-bar__secondary-area .e-admin-top-bar__secondary-area-buttons{display:-webkit-box;display:-ms-flexbox;display:flex}}
assets/css/admin-top-bar.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
  #e-dashboard-widget-admin-top-bar {
3
  position: absolute;
4
  opacity: 0;
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
  #e-dashboard-widget-admin-top-bar {
3
  position: absolute;
4
  opacity: 0;
assets/css/admin-top-bar.min.css CHANGED
@@ -1,2 +1,2 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
  #e-dashboard-widget-admin-top-bar{position:absolute;opacity:0;pointer-events:none}#e-admin-top-bar-root{right:0;background:#fff;-webkit-box-shadow:0 4px 6px rgba(0,0,0,.0394871);box-shadow:0 4px 6px rgba(0,0,0,.0394871);display:none;position:absolute;top:0;width:calc(100% - 160px);z-index:1}#e-admin-top-bar-root .e-admin-top-bar{display:-webkit-box;display:-ms-flexbox;display:flex;height:50px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:0 16px}#e-admin-top-bar-root .page-title-action{background:#f7f7f7;border:1px solid #0275d8;border-radius:3px;-webkit-box-sizing:border-box;box-sizing:border-box;color:#0275d8;cursor:pointer;display:inline-block;line-height:30px;margin:0 4px;min-height:30px;padding:0 10px;text-decoration:none}#e-admin-top-bar-root .page-title-action:hover{background:#eceeef;border-color:#1b607f;color:#1b607f}#e-admin-top-bar-root .e-admin-top-bar__heading{-webkit-margin-end:40px;margin-inline-end:40px}#e-admin-top-bar-root .e-admin-top-bar__heading,#e-admin-top-bar-root .e-admin-top-bar__main-area{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__main-area button{margin:0 4px}#e-admin-top-bar-root .e-admin-top-bar__secondary-area{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__heading-logo{width:30px;background:#93003c;border-radius:15px;color:#fff;display:inline-block;font-size:.875rem;height:1.875rem;line-height:1.875rem;text-align:center;width:1.875rem}#e-admin-top-bar-root .e-admin-top-bar__heading-title{color:#495157;font-size:15px;font-weight:700;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;padding:0 8px;line-height:normal;text-transform:uppercase}#e-admin-top-bar-root.e-admin-top-bar--active{display:block}#e-admin-top-bar-root.e-admin-top-bar--active~#wpbody #wpbody-content{margin-top:50px}#e-admin-top-bar-root.e-admin-top-bar--active~#wpbody .wrap{clear:both;padding-top:10px}#e-admin-top-bar-root.e-admin-top-bar--active~#wpbody .wrap h1{display:none}#e-admin-top-bar-root:not(.e-admin-top-bar--active)~#wpbody .wrap .page-title-action,#e-admin-top-bar-root:not(.e-admin-top-bar--active)~#wpbody .wrap h1{display:inline-block}#e-admin-top-bar-root .e-admin-top-bar__bar-button{-webkit-box-align:center;-ms-flex-align:center;align-items:center;cursor:pointer;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin:0 10px;text-decoration:none;color:#6d7882}#e-admin-top-bar-root .e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-title{color:#6d7882}#e-admin-top-bar-root .e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-icon{margin:0 4px}#e-admin-top-bar-root .e-admin-top-bar__bar-button:hover .e-admin-top-bar__bar-button-icon,#e-admin-top-bar-root .e-admin-top-bar__bar-button:hover .e-admin-top-bar__bar-button-title{color:#93003c}#e-admin-top-bar-root .e-admin-top-bar__bar-button-title{font-size:13px;font-weight:500;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;margin:0 4px;line-height:normal}#e-admin-top-bar-root~#wpbody .wrap .page-title-action,#e-admin-top-bar-root~#wpbody .wrap h1{display:none}@media screen and (max-width:960px){#e-admin-top-bar-root{width:calc(100% - 36px)}}@media screen and (max-width:782px){#e-admin-top-bar-root{width:100%}}@media screen and (max-width:600px){#e-admin-top-bar-root{top:46px}}@media (max-width:768px){#e-admin-top-bar-root{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__main-area-buttons{position:absolute;top:calc(100% + 10px)}#e-admin-top-bar-root .e-admin-top-bar__secondary-area .e-admin-top-bar__secondary-area-buttons,#e-admin-top-bar-root .e-admin-top-bar__secondary-area>.e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-title{display:none}}@media (min-width:768px){#e-admin-top-bar-root .e-admin-top-bar__secondary-area .e-admin-top-bar__secondary-area-buttons{display:-webkit-box;display:-ms-flexbox;display:flex}}
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
  #e-dashboard-widget-admin-top-bar{position:absolute;opacity:0;pointer-events:none}#e-admin-top-bar-root{right:0;background:#fff;-webkit-box-shadow:0 4px 6px rgba(0,0,0,.0394871);box-shadow:0 4px 6px rgba(0,0,0,.0394871);display:none;position:absolute;top:0;width:calc(100% - 160px);z-index:1}#e-admin-top-bar-root .e-admin-top-bar{display:-webkit-box;display:-ms-flexbox;display:flex;height:50px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:0 16px}#e-admin-top-bar-root .page-title-action{background:#f7f7f7;border:1px solid #0275d8;border-radius:3px;-webkit-box-sizing:border-box;box-sizing:border-box;color:#0275d8;cursor:pointer;display:inline-block;line-height:30px;margin:0 4px;min-height:30px;padding:0 10px;text-decoration:none}#e-admin-top-bar-root .page-title-action:hover{background:#eceeef;border-color:#1b607f;color:#1b607f}#e-admin-top-bar-root .e-admin-top-bar__heading{-webkit-margin-end:40px;margin-inline-end:40px}#e-admin-top-bar-root .e-admin-top-bar__heading,#e-admin-top-bar-root .e-admin-top-bar__main-area{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__main-area button{margin:0 4px}#e-admin-top-bar-root .e-admin-top-bar__secondary-area{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__heading-logo{width:30px;background:#93003c;border-radius:15px;color:#fff;display:inline-block;font-size:.875rem;height:1.875rem;line-height:1.875rem;text-align:center;width:1.875rem}#e-admin-top-bar-root .e-admin-top-bar__heading-title{color:#495157;font-size:15px;font-weight:700;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;padding:0 8px;line-height:normal;text-transform:uppercase}#e-admin-top-bar-root.e-admin-top-bar--active{display:block}#e-admin-top-bar-root.e-admin-top-bar--active~#wpbody #wpbody-content{margin-top:50px}#e-admin-top-bar-root.e-admin-top-bar--active~#wpbody .wrap{clear:both;padding-top:10px}#e-admin-top-bar-root.e-admin-top-bar--active~#wpbody .wrap h1{display:none}#e-admin-top-bar-root:not(.e-admin-top-bar--active)~#wpbody .wrap .page-title-action,#e-admin-top-bar-root:not(.e-admin-top-bar--active)~#wpbody .wrap h1{display:inline-block}#e-admin-top-bar-root .e-admin-top-bar__bar-button{-webkit-box-align:center;-ms-flex-align:center;align-items:center;cursor:pointer;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin:0 10px;text-decoration:none;color:#6d7882}#e-admin-top-bar-root .e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-title{color:#6d7882}#e-admin-top-bar-root .e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-icon{margin:0 4px}#e-admin-top-bar-root .e-admin-top-bar__bar-button:hover .e-admin-top-bar__bar-button-icon,#e-admin-top-bar-root .e-admin-top-bar__bar-button:hover .e-admin-top-bar__bar-button-title{color:#93003c}#e-admin-top-bar-root .e-admin-top-bar__bar-button-title{font-size:13px;font-weight:500;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;margin:0 4px;line-height:normal}#e-admin-top-bar-root~#wpbody .wrap .page-title-action,#e-admin-top-bar-root~#wpbody .wrap h1{display:none}@media screen and (max-width:960px){#e-admin-top-bar-root{width:calc(100% - 36px)}}@media screen and (max-width:782px){#e-admin-top-bar-root{width:100%}}@media screen and (max-width:600px){#e-admin-top-bar-root{top:46px}}@media (max-width:768px){#e-admin-top-bar-root{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#e-admin-top-bar-root .e-admin-top-bar__main-area-buttons{position:absolute;top:calc(100% + 10px)}#e-admin-top-bar-root .e-admin-top-bar__secondary-area .e-admin-top-bar__secondary-area-buttons,#e-admin-top-bar-root .e-admin-top-bar__secondary-area>.e-admin-top-bar__bar-button .e-admin-top-bar__bar-button-title{display:none}}@media (min-width:768px){#e-admin-top-bar-root .e-admin-top-bar__secondary-area .e-admin-top-bar__secondary-area-buttons{display:-webkit-box;display:-ms-flexbox;display:flex}}
assets/css/admin.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
  .elementor-button {
3
  font-family: Roboto, Arial, Helvetica, Verdana, sans-serif;
4
  font-weight: 500;
@@ -387,7 +387,7 @@ body .block-editor #elementor-switch-mode .button i {
387
  font-size: 50px;
388
  color: #a4afb7;
389
  }
390
- .elementor-blank_state h2 {
391
  font-size: 32px;
392
  font-weight: 300;
393
  color: inherit;
@@ -832,7 +832,7 @@ fieldset:disabled a.e-button {
832
  display: block;
833
  position: absolute;
834
  top: 0;
835
- right: 1px;
836
  border: none;
837
  margin: 0;
838
  padding: 9px;
@@ -1156,9 +1156,15 @@ fieldset:disabled a.e-button {
1156
  display: none;
1157
  }
1158
 
1159
- #elementor_rollback > div,
1160
- #elementor_rollback_pro > div,
1161
  #elementor_replace_url > div {
 
 
 
 
 
 
 
 
1162
  display: -webkit-box;
1163
  display: -ms-flexbox;
1164
  display: flex;
@@ -1166,9 +1172,7 @@ fieldset:disabled a.e-button {
1166
  #elementor_rollback > div input,
1167
  #elementor_rollback > div select,
1168
  #elementor_rollback_pro > div input,
1169
- #elementor_rollback_pro > div select,
1170
- #elementor_replace_url > div input,
1171
- #elementor_replace_url > div select {
1172
  margin-right: 6px;
1173
  }
1174
 
@@ -1645,8 +1649,10 @@ fieldset:disabled a.e-button {
1645
  -webkit-box-align: start;
1646
  -ms-flex-align: start;
1647
  align-items: flex-start;
1648
- -ms-flex-wrap: wrap;
1649
- flex-wrap: wrap;
 
 
1650
  }
1651
  .e-experiment__title__indicator {
1652
  position: absolute;
@@ -1680,25 +1686,21 @@ fieldset:disabled a.e-button {
1680
  .e-experiment__table-title {
1681
  margin: 30px 0;
1682
  }
1683
- .e-experiment__status {
 
1684
  font-size: 0.9em;
 
1685
  font-weight: bold;
1686
  font-style: italic;
1687
  }
1688
  .e-experiment__button.button {
1689
  margin: 18px 14px 22px 0;
1690
  }
1691
- .e-experiment__dependency__title {
1692
- color: #495157;
1693
  }
1694
- .e-experiment__dependency__item {
1695
- margin: 0 1px;
1696
- border: 1px solid;
1697
- padding: 2px;
1698
- font-size: 13px;
1699
- color: #6d7882;
1700
- font-weight: bold;
1701
- background: #f1f3f5;
1702
  }
1703
 
1704
  #tab-experiments .form-table tr {
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
  .elementor-button {
3
  font-family: Roboto, Arial, Helvetica, Verdana, sans-serif;
4
  font-weight: 500;
387
  font-size: 50px;
388
  color: #a4afb7;
389
  }
390
+ .elementor-blank_state h3 {
391
  font-size: 32px;
392
  font-weight: 300;
393
  color: inherit;
832
  display: block;
833
  position: absolute;
834
  top: 0;
835
+ inset-inline-end: 1px;
836
  border: none;
837
  margin: 0;
838
  padding: 9px;
1156
  display: none;
1157
  }
1158
 
 
 
1159
  #elementor_replace_url > div {
1160
+ max-width: 800px;
1161
+ }
1162
+ #elementor_replace_url > div input {
1163
+ margin-bottom: 6px;
1164
+ }
1165
+
1166
+ #elementor_rollback > div,
1167
+ #elementor_rollback_pro > div {
1168
  display: -webkit-box;
1169
  display: -ms-flexbox;
1170
  display: flex;
1172
  #elementor_rollback > div input,
1173
  #elementor_rollback > div select,
1174
  #elementor_rollback_pro > div input,
1175
+ #elementor_rollback_pro > div select {
 
 
1176
  margin-right: 6px;
1177
  }
1178
 
1649
  -webkit-box-align: start;
1650
  -ms-flex-align: start;
1651
  align-items: flex-start;
1652
+ -webkit-box-orient: vertical;
1653
+ -webkit-box-direction: normal;
1654
+ -ms-flex-direction: column;
1655
+ flex-direction: column;
1656
  }
1657
  .e-experiment__title__indicator {
1658
  position: absolute;
1686
  .e-experiment__table-title {
1687
  margin: 30px 0;
1688
  }
1689
+ .e-experiment__dependency, .e-experiment__status {
1690
+ margin-top: 4px;
1691
  font-size: 0.9em;
1692
+ line-height: 18px;
1693
  font-weight: bold;
1694
  font-style: italic;
1695
  }
1696
  .e-experiment__button.button {
1697
  margin: 18px 14px 22px 0;
1698
  }
1699
+ .e-experiment__dependency {
1700
+ color: #21759b;
1701
  }
1702
+ .e-experiment__dependency__title {
1703
+ font-weight: inherit;
 
 
 
 
 
 
1704
  }
1705
 
1706
  #tab-experiments .form-table tr {
assets/css/admin.min.css CHANGED
@@ -1,2 +1,2 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
- .elementor-button{font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-weight:500;text-transform:uppercase;outline:none;border:none;border-radius:3px;-webkit-transition-property:background,color,opacity,-webkit-box-shadow;transition-property:background,color,opacity,-webkit-box-shadow;-o-transition-property:background,color,box-shadow,opacity;transition-property:background,color,box-shadow,opacity;transition-property:background,color,box-shadow,opacity,-webkit-box-shadow;-webkit-transition-duration:.3s;-o-transition-duration:.3s;transition-duration:.3s}.elementor-button:hover{border:none}.elementor-button:not([disabled]){cursor:pointer}.elementor-button:not(.elementor-button-state) .elementor-state-icon{display:none}.elementor-button.elementor-button-success{color:#fff}.elementor-button.elementor-button-success[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-success:not([disabled]){background-color:#39b54a}.elementor-button.elementor-button-success:not([disabled]):hover{opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-success:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-brand{color:#fff}.elementor-button.elementor-button-brand[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-brand:not([disabled]){background-color:#93003c}.elementor-button.elementor-button-brand:not([disabled]):hover{opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-brand:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-warning{background-color:#a4afb7;color:#fff}.elementor-button.elementor-button-warning[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-warning:not([disabled]):hover{background-color:#b01b1b;opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-warning:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-danger{background-color:#d72b3f;color:#fff}.elementor-button.elementor-button-danger[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-danger:not([disabled]):hover{opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-danger:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-edit-template{display:inline-block;margin-top:15px;color:#fff}.elementor-button.elementor-button-default{background-color:#a4afb7;color:#fff;font-size:11px;padding:7px 21px}.elementor-button.elementor-button-default:hover{background-color:#6d7882;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-default:active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-default:visited{color:#fff}.elementor-button.elementor-button-go-pro{background-color:#93003c}.elementor-button i{margin-right:10px}#adminmenu #toplevel_page_elementor div.wp-menu-image:before{content:"\e813";font-family:eicons;font-size:18px;margin-top:1px}#adminmenu #toplevel_page_elementor a[href="admin.php?page=go_elementor_pro"]{font-weight:700}#adminmenu #toplevel_page_elementor a[href="admin.php?page=go_elementor_pro"]:hover{color:#e0005b}#adminmenu #toplevel_page_elementor .dashicons.dashicons-star-filled{height:auto}#adminmenu #menu-posts-elementor_library .wp-menu-image:before{content:"\e8ff";font-family:eicons;font-size:18px}#e-admin-menu__kit-library{color:#5cb85c}body.admin-color-fresh #adminmenu #toplevel_page_elementor a[href="admin.php?page=go_elementor_pro"]{color:#c60051}.elementor-plugins-gopro{color:#93003c;text-shadow:1px 1px 1px #eee;font-weight:700}#elementor-switch-mode{margin:15px 0}#elementor-editor-button,#elementor-switch-mode-button{outline:none;cursor:pointer}#elementor-editor-button i,#elementor-switch-mode-button i{margin-right:3px;font-size:125%;font-style:normal}body.elementor-editor-active .elementor-switch-mode-off{display:none}body.elementor-editor-active #elementor-switch-mode-button{background-color:#f7f7f7;color:#555;border-color:#ccc;-webkit-box-shadow:0 1px 0 #ccc!important;box-shadow:0 1px 0 #ccc!important;text-shadow:unset}body.elementor-editor-active #elementor-switch-mode-button:hover{background-color:#e9e9e9}body.elementor-editor-active #elementor-switch-mode-button:active{-webkit-box-shadow:inset 0 1px 0 #ccc;box-shadow:inset 0 1px 0 #ccc;-webkit-transform:translateY(1px);-ms-transform:translateY(1px);transform:translateY(1px)}body.elementor-editor-active #postdivrich{display:none!important}body.elementor-editor-active .block-editor-block-list__layout,body.elementor-editor-active .editor-block-list__layout,body.elementor-editor-inactive #elementor-editor,body.elementor-editor-inactive .elementor-switch-mode-on{display:none}body.elementor-editor-active .edit-post-layout__content .edit-post-visual-editor{-ms-flex-preferred-size:auto;flex-basis:auto}body.elementor-editor-active #elementor-editor{margin-bottom:50px}body.elementor-editor-active .edit-post-text-editor__body .editor-post-text-editor{display:none}body .block-editor #elementor-switch-mode{margin:0 15px}body .block-editor #elementor-switch-mode .button{margin:2px;height:33px;font-size:13px;line-height:1}body .block-editor #elementor-switch-mode .button i{padding-right:5px}.elementor-button{font-size:13px;text-decoration:none;padding:15px 40px}#elementor-editor{height:300px;width:100%;-webkit-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease}#elementor-editor .elementor-loader-wrapper{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:300px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#elementor-editor .elementor-loader{border-radius:50%;padding:40px;height:150px;width:150px;background-color:hsla(0,0%,100%,.9);-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:2px 2px 20px 4px rgba(0,0,0,.02);box-shadow:2px 2px 20px 4px rgba(0,0,0,.02)}#elementor-editor .elementor-loader-boxes{height:100%;width:100%;position:relative}#elementor-editor .elementor-loader-box{position:absolute;background-color:#d5dadf;-webkit-animation:load 1.8s linear infinite;animation:load 1.8s linear infinite}#elementor-editor .elementor-loader-box:first-of-type{width:20%;height:100%;left:0;top:0}#elementor-editor .elementor-loader-box:not(:first-of-type){right:0;height:20%;width:60%}#elementor-editor .elementor-loader-box:nth-of-type(2){top:0;-webkit-animation-delay:calc(1.8s / 4 * -1);animation-delay:calc(1.8s / 4 * -1)}#elementor-editor .elementor-loader-box:nth-of-type(3){top:40%;-webkit-animation-delay:calc(1.8s / 4 * -2);animation-delay:calc(1.8s / 4 * -2)}#elementor-editor .elementor-loader-box:nth-of-type(4){bottom:0;-webkit-animation-delay:calc(1.8s / 4 * -3);animation-delay:calc(1.8s / 4 * -3)}#elementor-editor .elementor-loading-title{color:#a4afb7;text-align:center;text-transform:uppercase;margin-top:30px;letter-spacing:7px;text-indent:7px;font-size:10px;width:100%}#elementor-go-to-edit-page-link{height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid #ddd;background-color:#f7f7f7;text-decoration:none;position:relative;font-family:Sans-serif}#elementor-go-to-edit-page-link:hover{background-color:#fff}#elementor-go-to-edit-page-link:focus{-webkit-box-shadow:none;box-shadow:none}#elementor-go-to-edit-page-link.elementor-animate #elementor-editor-button,#elementor-go-to-edit-page-link:not(.elementor-animate) .elementor-loader-wrapper{display:none}.elementor-button-spinner:before{font:normal 20px/.5 dashicons;speak:none;display:inline-block;padding:0;top:8px;left:-4px;position:relative;vertical-align:top;content:"\f463"}.elementor-button-spinner.loading:before{-webkit-animation:rotation 1s linear infinite;animation:rotation 1s linear infinite}.elementor-button-spinner.success:before{content:"\f147";color:#46b450}.elementor-blank_state{padding:5em 0;margin:auto;max-width:520px;text-align:center;color:#6d7882;font-family:Roboto,sans-serif}.elementor-blank_state i{font-size:50px;color:#a4afb7}.elementor-blank_state h2{font-size:32px;font-weight:300;color:inherit;margin:40px 0 10px;line-height:1.2}.elementor-blank_state p{font-size:16px;font-weight:400;color:#a4afb7;margin-bottom:40px}.elementor-blank_state .elementor-button{display:inline-block}#available-widgets [class*=elementor-template] .widget-title:before{content:"\e813";font-family:eicons;font-size:17px}.elementor-settings-form-page{padding-top:30px}._elementor_settings_update_time,.elementor-settings-form-page:not(.elementor-active){display:none}#confirm_fa_migration_admin_modal .dialog-confirm-ok{color:#6d7882}body.post-type-attachment table.media .column-title .media-icon img[src$=".svg"]{width:100%}.e-major-update-warning{margin-bottom:5px;max-width:1000px;display:-webkit-box;display:-ms-flexbox;display:flex}.e-major-update-warning__separator{margin:15px -12px}.e-major-update-warning__icon{font-size:17px;margin-right:9px;margin-left:2px}.e-major-update-warning__title{font-weight:600;margin-bottom:10px}.e-major-update-warning+p{display:none}.notice-success .e-major-update-warning__separator{border:1px solid #46b450}.notice-success .e-major-update-warning__icon{color:#79ba49}.notice-warning .e-major-update-warning__separator{border:1px solid #ffb900}.notice-warning .e-major-update-warning__icon{color:#f56e28}.plugins table.e-compatibility-update-table tr{background:transparent}.plugins table.e-compatibility-update-table tr th{font-weight:600}.plugins table.e-compatibility-update-table tr td,.plugins table.e-compatibility-update-table tr th{min-width:250px;font-size:13px;background:transparent;-webkit-box-shadow:none;box-shadow:none;border:none;padding:5px 15px 5px 0}:root{--e-focus-color:rgba(0,115,170,0.4);--e-context-primary-color:#0073aa;--e-context-primary-color-dark:#005177;--e-context-primary-tint-4:rgba(0,115,170,0.4);--e-context-primary-tint-1:rgba(0,115,170,0.04);--e-context-success-color:#39b54a;--e-context-success-color-dark:#2d8e3a;--e-context-success-tint-4:rgba(57,181,74,0.4);--e-context-success-tint-1:rgba(57,181,74,0.04);--e-context-info-color:#71d7f7;--e-context-info-color-dark:#41c9f4;--e-context-info-tint-4:rgba(113,215,247,0.4);--e-context-info-tint-1:rgba(113,215,247,0.04);--e-context-warning-color:#fcb92c;--e-context-warning-color-dark:#f2a503;--e-context-warning-tint-4:rgba(252,185,44,0.4);--e-context-warning-tint-1:rgba(252,185,44,0.04);--e-context-error-color:#d72b3f;--e-context-error-color-dark:#ae2131;--e-context-error-tint-4:rgba(215,43,63,0.4);--e-context-error-tint-1:rgba(215,43,63,0.04);--e-context-cta-color:#93003c;--e-context-cta-color-dark:#600027;--e-context-cta-tint-4:rgba(147,0,60,0.4);--e-context-cta-tint-1:rgba(147,0,60,0.04)}.e-getting-started{max-width:900px;padding:2.5em 0;margin:auto;text-align:center}.e-getting-started__header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-shadow:0 0 8px rgba(0,0,0,.1);box-shadow:0 0 8px rgba(0,0,0,.1)}.e-getting-started__header .e-logo-wrapper{font-size:10px;margin-right:10px}.e-getting-started__title{padding:0 15px;font-weight:600;text-transform:uppercase;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.e-getting-started__skip{border-left:1px solid #eee;font-size:16px;color:inherit}.e-getting-started__skip i{padding:15px}.e-getting-started__content{padding:50px}.e-getting-started__content h2{font-size:2em;margin-top:0}.e-getting-started__content--narrow{max-width:500px;margin:auto}.e-getting-started__video{margin:40px 0 60px}.e-getting-started__video iframe{-webkit-box-shadow:10px 10px 20px rgba(0,0,0,.15);box-shadow:10px 10px 20px rgba(0,0,0,.15)}.e-getting-started__actions .button-primary{margin-right:20px}:root{--e-button-padding-y:0.4375rem;--e-button-padding-x:0.75rem;--e-button-font-size:0.8125rem;--e-button-font-weight:500;--e-button-line-height:0.9375rem;--e-button-border-radius:3px;--e-button-context-color:var(--e-context-primary-color);--e-button-context-color-dark:var(--e-context-primary-color-dark);--e-button-context-tint:var(--e-context-primary-tint-1)}.e-button{display:inline-block;font-weight:var(--e-button-font-weight);text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:#fff;border:0;text-decoration:none;background:var(--e-button-context-color);padding:var(--e-button-padding-y) var(--e-button-padding-x);font-size:var(--e-button-font-size);line-height:var(--e-button-line-height);border-radius:var(--e-button-border-radius);-webkit-transition:background-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;-o-transition:background-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out}.e-button:active,.e-button:focus,.e-button:hover{color:#fff;text-decoration:none;background:var(--e-button-context-color-dark)}.e-button.focus,.e-button:focus{outline:0;-webkit-box-shadow:0 0 0 2px var(--e-focus-color);box-shadow:0 0 0 2px var(--e-focus-color)}.e-button.disabled,.e-button:disabled{opacity:.5;-webkit-box-shadow:none;box-shadow:none}.e-button:not(:disabled):not(.disabled){cursor:pointer}.e-button:not(:disabled):not(.disabled).active:focus,.e-button:not(:disabled):not(.disabled):active:focus{-webkit-box-shadow:0 0 0 2px var(--e-focus-color);box-shadow:0 0 0 2px var(--e-focus-color)}.e-button--primary{--e-button-context-color:var(--e-context-primary-color);--e-button-context-color-dark:var(--e-context-primary-color-dark);--e-button-context-tint:var(--e-context-primary-tint-1);--e-focus-color:var(--e-context-primary-tint-4)}.e-button--success{--e-button-context-color:var(--e-context-success-color);--e-button-context-color-dark:var(--e-context-success-color-dark);--e-button-context-tint:var(--e-context-success-tint-1);--e-focus-color:var(--e-context-success-tint-4)}.e-button--info{--e-button-context-color:var(--e-context-info-color);--e-button-context-color-dark:var(--e-context-info-color-dark);--e-button-context-tint:var(--e-context-info-tint-1);--e-focus-color:var(--e-context-info-tint-4)}.e-button--warning{--e-button-context-color:var(--e-context-warning-color);--e-button-context-color-dark:var(--e-context-warning-color-dark);--e-button-context-tint:var(--e-context-warning-tint-1);--e-focus-color:var(--e-context-warning-tint-4)}.e-button--error{--e-button-context-color:var(--e-context-error-color);--e-button-context-color-dark:var(--e-context-error-color-dark);--e-button-context-tint:var(--e-context-error-tint-1);--e-focus-color:var(--e-context-error-tint-4)}.e-button--cta{--e-button-context-color:var(--e-context-cta-color);--e-button-context-color-dark:var(--e-context-cta-color-dark);--e-button-context-tint:var(--e-context-cta-tint-1);--e-focus-color:var(--e-context-cta-tint-4)}.e-button.e-button--outline{color:var(--e-button-context-color);background:none;border:1px solid}.e-button.e-button--outline:focus,.e-button.e-button--outline:hover{color:var(--e-button-context-color-dark);background:var(--e-button-context-tint)}.e-button.e-button--outline.disabled,.e-button.e-button--outline:disabled{color:var(--e-button-context-color-dark);background:#818a91}.e-button>i{line-height:inherit;height:var(--e-button-line-height);width:-webkit-min-content;width:-moz-min-content;width:min-content}.e-button>*+*{-webkit-margin-start:.5ch;margin-inline-start:.5ch}.e-button--link{color:var(--e-button-context-color);background-color:transparent}.e-button--link:focus,.e-button--link:hover{color:var(--e-button-context-color-dark);background:var(--e-button-context-tint)}.e-button--link.disabled,.e-button--link:disabled{color:#818a91}a.e-button.disabled,fieldset:disabled a.e-button{pointer-events:none}:root{--e-notice-bg:#fff;--e-notice-border-color:#ccd0d4;--e-notice-context-color:#93003c;--e-notice-context-tint:var(--e-context-cta-tint-1);--e-notice-box-shadow:0 1px 4px rgba(0,0,0,0.15);--e-notice-dismiss-color:#6d7882}.e-notice{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;background:var(--e-notice-bg);border:1px solid var(--e-notice-border-color);border-inline-start-width:4px;-webkit-box-shadow:var(--e-notice-box-shadow);box-shadow:var(--e-notice-box-shadow);margin:5px 20px 5px 2px}.e-notice.notice{padding:0}.e-notice:before{display:block;content:"";position:absolute;left:-4px;top:-1px;bottom:-1px;width:4px;background-color:var(--e-notice-context-color)}.e-notice--primary{--e-notice-context-color:var(--e-context-primary-color);--e-notice-context-color-dark:var(--e-context-primary-color-dark);--e-notice-context-tint:var(--e-context-primary-tint-1)}.e-notice--success{--e-notice-context-color:var(--e-context-success-color);--e-notice-context-color-dark:var(--e-context-success-color-dark);--e-notice-context-tint:var(--e-context-success-tint-1)}.e-notice--info{--e-notice-context-color:var(--e-context-info-color);--e-notice-context-color-dark:var(--e-context-info-color-dark);--e-notice-context-tint:var(--e-context-info-tint-1)}.e-notice--warning{--e-notice-context-color:var(--e-context-warning-color);--e-notice-context-color-dark:var(--e-context-warning-color-dark);--e-notice-context-tint:var(--e-context-warning-tint-1)}.e-notice--error{--e-notice-context-color:var(--e-context-error-color);--e-notice-context-color-dark:var(--e-context-error-color-dark);--e-notice-context-tint:var(--e-context-error-tint-1)}.e-notice--cta{--e-notice-context-color:var(--e-context-cta-color);--e-notice-context-color-dark:var(--e-context-cta-color-dark);--e-notice-context-tint:var(--e-context-cta-tint-1)}.e-notice--extended{--e-notice-is-extended:1}.e-notice--dismissible{padding-right:38px}.e-notice__aside{overflow:hidden;background-color:var(--e-notice-context-tint);width:calc(var(--e-notice-is-extended, 0) * 50px);text-align:center;padding-top:15px;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.e-notice__icon-wrapper{display:inline-block;font-size:.625rem;max-height:1.5rem;width:1.5rem;line-height:1.5rem;border-radius:100px;background:var(--e-notice-context-color);color:#fff;text-shadow:0 0 3px var(--e-notice-context-color-dark),0 0 1px var(--e-notice-context-color-dark),0 0 1px var(--e-notice-context-color-dark)}.e-notice__content{padding:20px}.e-notice__actions{display:-webkit-box;display:-ms-flexbox;display:flex}.e-notice__actions>*+*{-webkit-margin-start:8px;margin-inline-start:8px}.e-notice__dismiss{width:20px;height:20px;line-height:20px;font-size:.8125rem;text-align:center;background:none;display:block;position:absolute;top:0;right:1px;border:none;margin:0;padding:9px;cursor:pointer;font-style:normal}.e-notice__dismiss:before{font-family:eicons;display:inline-block;content:"\e87f";color:var(--e-notice-dismiss-color);width:20px;border-radius:20px;speak:none;text-align:center;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.e-notice__dismiss:active:before,.e-notice__dismiss:focus:before,.e-notice__dismiss:hover:before{font-weight:700}.e-notice__dismiss:focus:before{color:#fff;background:var(--e-notice-dismiss-color);outline:none}.e-notice__dismiss:focus{outline:none}.e-notice p{line-height:1.2;padding:0;margin:0}.e-notice p+.e-notice__actions{margin-top:1rem}.e-notice h3{font-size:1.0625rem;line-height:1.2;margin:0}.e-notice h3+p{margin-top:8px}.elementor-admin-alert{padding:15px;border-left:5px solid transparent;position:relative;font-size:12px;line-height:1.5;text-align:left}.elementor-admin-alert a{color:inherit}.elementor-admin-alert.elementor-alert-info{color:#31708f;background-color:#d9edf7;border-color:#bcdff1}.elementor-admin-alert.elementor-alert-success{color:#3c763d;background-color:#dff0d8;border-color:#cae6be}.elementor-admin-alert.elementor-alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#f9f0c3}.elementor-admin-alert.elementor-alert-danger{color:#a94442;background-color:#f2dede;border-color:#e8c4c4}#elementor-system-info{padding:15px}#elementor-system-info .elementor-system-info-section{margin-bottom:10px}#elementor-system-info .elementor-system-info-section>.elementor-system-info-report-name{padding-left:10px;border-bottom:1px solid #e1e1e1}#elementor-system-info .elementor-system-info-section .widefat{white-space:pre}#elementor-system-info .elementor-system-info-section .elementor-log-entries{white-space:pre-wrap}#elementor-system-info .elementor-system-info-section:not(.elementor-system-info-log) tbody td:first-child{width:300px}#elementor-system-info .elementor-system-info-report-name{text-transform:uppercase;font-size:14px;margin:0;line-height:2}#elementor-system-info .elementor-system-info-report-row{overflow:hidden;padding:5px 0}#elementor-system-info .elementor-system-info-report-row>*{float:left}#elementor-system-info .elementor-system-info-field-recommendation,#elementor-system-info .elementor-system-info-report-field{padding-left:10px;color:#7f7f7f}#elementor-system-info .elementor-system-info-report-fields{padding-left:20px}#elementor-system-info .elementor-system-info-plugin-name{color:#000}#elementor-system-info .elementor-system-info-plugin-properties{padding:10px}#elementor-system-info #elementor-system-info-raw-code{width:100%;height:200px}#elementor-system-info #elementor-system-info-raw-code-label{padding:5px;display:block}#elementor-system-info .elementor-warning td:first-child{border-left:3px solid #fcb92c}#elementor-system-info a.box-title-tool{font-size:80%;margin-left:15px;color:#818a91}#elementor-system-info a.box-title-tool:hover{text-decoration:underline}#elementor-system-info #elementor-usage-recalc{font-size:12px;color:#fff;background-color:#a4afb7;padding:4px 18px 5px;border-radius:3px}@-webkit-keyframes elementor-rotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes elementor-rotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}#elementor-deactivate-feedback-dialog-wrapper{display:none}#elementor-deactivate-feedback-modal .dialog-widget-content{width:550px}#elementor-deactivate-feedback-modal .dialog-header{padding:18px 15px;-webkit-box-shadow:0 0 8px rgba(0,0,0,.1);box-shadow:0 0 8px rgba(0,0,0,.1);text-align:left}#elementor-deactivate-feedback-modal .dialog-message{padding:30px 30px 0;text-align:left}#elementor-deactivate-feedback-modal .elementor-deactivate-feedback-dialog-input{float:left;margin:0 15px 0 0;-webkit-box-shadow:none;box-shadow:none}#elementor-deactivate-feedback-modal .elementor-deactivate-feedback-dialog-input:not(:checked)~.elementor-feedback-text{display:none}#elementor-deactivate-feedback-modal .elementor-deactivate-feedback-dialog-label{display:block;font-size:13px;color:#6d7882}#elementor-deactivate-feedback-modal .elementor-feedback-text{margin:10px 0 0 30px;padding:5px;font-size:13px;-webkit-box-shadow:none;box-shadow:none;background-color:#fff;width:92%}#elementor-deactivate-feedback-modal .dialog-buttons-wrapper{border-top:none;text-align:left;padding:20px 30px 30px;overflow:hidden}#elementor-deactivate-feedback-modal .dialog-submit{background-color:#93003c;border-radius:3px;color:#fff;line-height:1;padding:12px 20px;font-size:13px;width:180px;height:38px}#elementor-deactivate-feedback-modal .dialog-submit.elementor-loading:before{display:inline-block;content:"\f463";font:18px dashicons;-webkit-animation:elementor-rotation 2s linear infinite;animation:elementor-rotation 2s linear infinite}#elementor-deactivate-feedback-modal .dialog-skip{font-size:12px;color:#a4afb7;background:none;float:right;width:auto}#elementor-deactivate-feedback-modal[data-feedback-selected=elementor_pro] .elementor-feedback-text{color:#b01b1b;padding:0}#elementor-deactivate-feedback-modal[data-feedback-selected=elementor_pro] .dialog-submit{display:none}#elementor-deactivate-feedback-dialog-header i{color:#93003c;font-size:19px}#elementor-deactivate-feedback-dialog-header-title{font-size:15px;text-transform:uppercase;font-weight:700;padding-left:5px}#elementor-deactivate-feedback-dialog-form-caption{font-weight:700;font-size:15px;color:#495157;line-height:1.4}#elementor-deactivate-feedback-dialog-form-body{padding-top:30px}.elementor-deactivate-feedback-dialog-input-wrapper{line-height:1;overflow:hidden;margin-bottom:15px}#elementor-hidden-area{display:none}#elementor-import-template-trigger{cursor:pointer}#elementor-import-template-area{display:none;margin:50px 0 30px;text-align:center}#elementor-import-template-form{display:inline-block;margin-top:30px;padding:30px 50px;background-color:#fff;border:1px solid #e5e5e5}#elementor-import-template-title{font-size:18px;color:#555d66}.form-table:not(.elementor-maintenance-mode-is-enabled) .elementor-default-hide{display:none}.elementor-maintenance-mode-error{color:red;line-height:1.6;display:none}#tab-fontawesome4_migration.elementor-active~p.submit,#tab-import-export-kit.elementor-active~p.submit,#tab-replace_url.elementor-active~p.submit{display:none}#elementor_replace_url>div,#elementor_rollback>div,#elementor_rollback_pro>div{display:-webkit-box;display:-ms-flexbox;display:flex}#elementor_replace_url>div input,#elementor_replace_url>div select,#elementor_rollback>div input,#elementor_rollback>div select,#elementor_rollback_pro>div input,#elementor_rollback_pro>div select{margin-right:6px}.tab-import-export-kit__wrapper{margin:40px 0;max-width:700px}.tab-import-export-kit__container{background-color:#fff;font-size:16px;max-width:700px;padding:30px}.tab-import-export-kit__container:not(:first-child){margin-top:5px}.tab-import-export-kit__container p{color:#a4afb7;font-size:16px;margin:20px 0 25px}.tab-import-export-kit__info{font-size:14px}.tab-import-export-kit__container a:not(.elementor-button),.tab-import-export-kit__info a{color:#58d0f5;text-decoration:underline}.tab-import-export-kit__box{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.tab-import-export-kit__box h2{color:#6d7882;font-size:28px;font-weight:400;line-height:1;margin:0}.tab-import-export-kit__box .elementor-button.elementor-button-success{font-weight:700;padding:8px 16px;text-transform:none}#dashboard-widgets .e-dashboard-widget h3.e-heading{font-weight:600;margin-bottom:13px}#dashboard-widgets .e-dashboard-widget .e-divider_bottom{border-bottom:1px solid #eee;margin:0 -12px;padding:6px 12px}#dashboard-widgets .e-dashboard-widget .e-divider_top{border-top:1px solid #eee;margin:0 -12px;padding:6px 12px}#dashboard-widgets .e-dashboard-widget .e-news-feed-wrap .e-divider_top,#dashboard-widgets .e-dashboard-widget .e-quick-actions-wrap .e-divider_top{padding-top:18px;margin-top:18px}.e-dashboard-widget .dashicons{color:#606a73}.e-dashboard-widget ul.e-action-list li{margin-top:14px}.e-dashboard-widget ul.e-action-list li a{margin-left:5px}#e-dashboard-overview .dashicons{vertical-align:middle;font-size:17px}#e-dashboard-overview .e-overview__header{display:table;width:100%;-webkit-box-shadow:0 5px 8px rgba(0,0,0,.05);box-shadow:0 5px 8px rgba(0,0,0,.05);margin:0 -12px 8px;padding:0 12px 12px}#e-dashboard-overview .e-overview__create,#e-dashboard-overview .e-overview__logo,#e-dashboard-overview .e-overview__versions{display:table-cell;vertical-align:middle}#e-dashboard-overview .e-overview__logo{width:30px}#e-dashboard-overview .e-overview__versions{padding:0 10px;font-size:.9em;line-height:1.5}#e-dashboard-overview .e-overview__version{display:block}#e-dashboard-overview .e-overview__create{text-align:right}#e-dashboard-overview .e-overview__feed{font-size:14px;font-weight:500}#e-dashboard-overview .e-overview__post{margin-top:10px}#e-dashboard-overview .e-overview__post-link{display:inline-block}#e-dashboard-overview .e-overview__badge{background:#39b54a;color:#fff;font-size:.75em;padding:3px 6px;border-radius:3px;text-transform:uppercase}#e-dashboard-overview .e-overview__post-description{margin:0 0 1.5em}#e-dashboard-overview .e-overview__recently-edited li{color:#72777c}#e-dashboard-overview .e-overview__footer.e-divider_top{padding-top:12px;padding-bottom:0}#e-dashboard-overview .e-overview__footer ul{display:-webkit-box;display:-ms-flexbox;display:flex;list-style:none;margin:0;padding:0}#e-dashboard-overview .e-overview__footer ul li{padding:0 10px;margin:0;border-left:1px solid #ddd}#e-dashboard-overview .e-overview__footer ul li:first-child{padding-left:0;border:none}#e-dashboard-overview .e-overview__go-pro a{color:#93003c;font-weight:500}.post-type-elementor_library #elementor-template-library-tabs-wrapper{padding-top:2em;margin-bottom:2em}.post-type-elementor_library th#taxonomy-elementor_library_category{width:110px}#elementor-new-template-modal .dialog-message{max-height:70vh}#elementor-new-template-modal .e-hidden{display:none!important}#elementor-new-template-dialog-content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:100%;text-align:left;color:#6d7882}@media (max-width:1439px){#elementor-new-template-dialog-content{padding:0 50px}}@media (min-width:1440px){#elementor-new-template-dialog-content{padding:0 120px}}#elementor-new-template__description{width:35%;max-width:300px;padding-right:100px}#elementor-new-template__description__title{font-size:30px;color:#556068}#elementor-new-template__description__title span{font-weight:700}#elementor-new-template__description__content{font-size:16px;padding:30px 0}#elementor-new-template__take_a_tour{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:15px}#elementor-new-template__take_a_tour i{color:#93003c;font-size:30px}#elementor-new-template__take_a_tour a{color:#6d7882;padding-left:10px;text-decoration:none;font-weight:500}#elementor-new-template__form{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:440px;padding:55px;background-color:#fff;border-radius:3px;-webkit-box-shadow:0 2px 30px 0 rgba(0,0,0,.08);box-shadow:0 2px 30px 0 rgba(0,0,0,.08)}#elementor-new-template__form__title{font-size:23px;color:#556068}#elementor-new-template__form__template-type.elementor-form-field__select{max-width:none}#elementor-new-template__form__template-type-badge{position:absolute;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:2px;border-radius:2px;background-color:#f1f3f5;padding:4px;font-size:8px;font-weight:500;line-height:1;text-transform:uppercase;top:50%;inset-inline-end:28px;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}#elementor-new-template__form .elementor-form-field__label{display:block;margin:25px 0 7px;font-size:14px;line-height:1}#elementor-new-template__form .elementor-form-field input,#elementor-new-template__form .elementor-form-field select{width:100%;height:50px;padding:10px;font-size:14px;-webkit-box-shadow:none;box-shadow:none;border-radius:3px;background:none;color:#495157;border:1px solid;outline:none}#elementor-new-template__form .elementor-form-field input:not(:focus),#elementor-new-template__form .elementor-form-field select:not(:focus){border-color:#d5dadf}#elementor-new-template__form .elementor-form-field input:focus,#elementor-new-template__form .elementor-form-field select:focus{border-color:#a4afb7}#elementor-new-template__form .elementor-form-field__select{appearance:none;-webkit-appearance:none;-moz-appearance:none;cursor:pointer}#elementor-new-template__form .elementor-form-field__select__wrapper{position:relative}#elementor-new-template__form .elementor-form-field__select__wrapper:after{font-family:eicons;content:"\e8ad";position:absolute;top:50%;right:10px;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}#elementor-new-template__form__lock_button,#elementor-new-template__form__submit{display:block;width:100%;height:50px;margin-top:24px;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;text-transform:none}@media (max-width:1024px){#elementor-new-template__description{max-width:250px;padding-right:30px}}@media (max-width:767px){#elementor-new-template__description{display:none}}#elementor-role-manager{max-width:500px;margin-top:50px}#elementor-role-manager h3{color:#6d7882;font-weight:400;font-size:22px}#elementor-role-manager .elementor-settings-form-page{padding:0}#elementor-role-manager .elementor-role-row{background:#fff;color:#6d7882;margin-bottom:2px}#elementor-role-manager .elementor-role-row .elementor-role-label{display:-webkit-box;display:-ms-flexbox;display:flex;padding:15px 20px;font-weight:500;cursor:pointer}#elementor-role-manager .elementor-role-row .elementor-role-label span.elementor-role-name{padding-right:20px}#elementor-role-manager .elementor-role-row .elementor-role-label span.elementor-role-toggle{text-align:right;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}#elementor-role-manager .elementor-role-row .elementor-role-label span.elementor-role-excluded-indicator{color:#a4afb7}#elementor-role-manager .elementor-role-row .elementor-role-controls{background-color:#f7f7f7;padding:20px 20px 5px}#elementor-role-manager .elementor-role-row .elementor-role-controls>div{margin-bottom:15px}#elementor-role-manager .elementor-role-row .elementor-role-controls .elementor-role-go-pro{display:-webkit-box;display:-ms-flexbox;display:flex}#elementor-role-manager .elementor-role-row .elementor-role-controls .elementor-role-go-pro .elementor-role-go-pro__desc{font-weight:500;font-style:italic}#elementor-role-manager .elementor-role-row .elementor-role-controls .elementor-role-go-pro .elementor-role-go-pro__link{text-align:right;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area{color:#c2cbd2;cursor:pointer}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area:hover .elementor-beta-tester-do-not-show-again,#elementor-beta-tester-modal .elementor-templates-modal__header__items-area:hover .elementor-templates-modal__header__item>i{color:#6d7882}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area .elementor-templates-modal__header__close{border:none}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area .elementor-beta-tester-do-not-show-again{text-transform:uppercase;font-weight:700;font-size:12px;-webkit-transition:all .3s;-o-transition:all .3s;transition:all .3s}#elementor-beta-tester-modal .dialog-lightbox-widget-content{max-width:500px;height:auto}#elementor-beta-tester-modal .dialog-lightbox-message{padding:40px;height:300px;background-color:#fff}#elementor-beta-tester-form__caption{font-weight:700;font-size:20px;color:#495157}#elementor-beta-tester-form__description{font-size:15px;color:#6d7882;margin-top:10px}#elementor-beta-tester-form__input-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;margin-top:30px}#elementor-beta-tester-form__input-wrapper .elementor-button{border-radius:0 3px 3px 0}#elementor-beta-tester-form__email{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;border:1px solid #d5dadf;border-right:0;border-radius:3px 0 0 3px;margin:0;padding:10px;height:50px}#elementor-beta-tester-form__terms{margin-top:40px;font-size:11px;color:#a4afb7}.e-experiment__title{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-ms-flex-wrap:wrap;flex-wrap:wrap}.e-experiment__title__indicator{position:absolute;height:10px;width:10px;border-radius:50%;border:2px solid #fff;-webkit-box-shadow:0 2px 4px rgba(0,0,0,.1);box-shadow:0 2px 4px rgba(0,0,0,.1);-ms-flex-negative:0;flex-shrink:0;margin-top:2px}.e-experiment__title__indicator--active{background:#39b54a}.e-experiment__title__label{margin-left:24px}.e-experiment__title__tag{background:#0085ba;color:#fff;font-size:.8em;padding:3px 6px;line-height:1;border-radius:3px;font-weight:600;margin-top:5px;margin-left:24px}.e-experiment__table-title{margin:30px 0}.e-experiment__status{font-size:.9em;font-weight:700;font-style:italic}.e-experiment__button.button{margin:18px 14px 22px 0}.e-experiment__dependency__title{color:#495157}.e-experiment__dependency__item{margin:0 1px;border:1px solid;padding:2px;font-size:13px;color:#6d7882;font-weight:700;background:#f1f3f5}#tab-experiments .form-table tr{border-bottom:1px solid #dcdcde}#tab-experiments .form-table tr:last-child{border-bottom:none}#tab-experiments .form-table tr .description{font-size:.9em;margin:10px 0;max-width:820px}.e-landing-pages-empty .elementor-blank_state{padding:5em 0 2em}.e-landing-pages-empty .e-trashed-items{text-align:center}
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
+ .elementor-button{font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-weight:500;text-transform:uppercase;outline:none;border:none;border-radius:3px;-webkit-transition-property:background,color,opacity,-webkit-box-shadow;transition-property:background,color,opacity,-webkit-box-shadow;-o-transition-property:background,color,box-shadow,opacity;transition-property:background,color,box-shadow,opacity;transition-property:background,color,box-shadow,opacity,-webkit-box-shadow;-webkit-transition-duration:.3s;-o-transition-duration:.3s;transition-duration:.3s}.elementor-button:hover{border:none}.elementor-button:not([disabled]){cursor:pointer}.elementor-button:not(.elementor-button-state) .elementor-state-icon{display:none}.elementor-button.elementor-button-success{color:#fff}.elementor-button.elementor-button-success[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-success:not([disabled]){background-color:#39b54a}.elementor-button.elementor-button-success:not([disabled]):hover{opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-success:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-brand{color:#fff}.elementor-button.elementor-button-brand[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-brand:not([disabled]){background-color:#93003c}.elementor-button.elementor-button-brand:not([disabled]):hover{opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-brand:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-warning{background-color:#a4afb7;color:#fff}.elementor-button.elementor-button-warning[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-warning:not([disabled]):hover{background-color:#b01b1b;opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-warning:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-danger{background-color:#d72b3f;color:#fff}.elementor-button.elementor-button-danger[disabled]{background-color:#c2cbd2}.elementor-button.elementor-button-danger:not([disabled]):hover{opacity:.85;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-danger:not([disabled]):active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-edit-template{display:inline-block;margin-top:15px;color:#fff}.elementor-button.elementor-button-default{background-color:#a4afb7;color:#fff;font-size:11px;padding:7px 21px}.elementor-button.elementor-button-default:hover{background-color:#6d7882;-webkit-box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2);box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.2)}.elementor-button.elementor-button-default:active{-webkit-box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1);box-shadow:0 5px 10px rgba(0,0,0,.19),0 3px 3px rgba(0,0,0,.1)}.elementor-button.elementor-button-default:visited{color:#fff}.elementor-button.elementor-button-go-pro{background-color:#93003c}.elementor-button i{margin-right:10px}#adminmenu #toplevel_page_elementor div.wp-menu-image:before{content:"\e813";font-family:eicons;font-size:18px;margin-top:1px}#adminmenu #toplevel_page_elementor a[href="admin.php?page=go_elementor_pro"]{font-weight:700}#adminmenu #toplevel_page_elementor a[href="admin.php?page=go_elementor_pro"]:hover{color:#e0005b}#adminmenu #toplevel_page_elementor .dashicons.dashicons-star-filled{height:auto}#adminmenu #menu-posts-elementor_library .wp-menu-image:before{content:"\e8ff";font-family:eicons;font-size:18px}#e-admin-menu__kit-library{color:#5cb85c}body.admin-color-fresh #adminmenu #toplevel_page_elementor a[href="admin.php?page=go_elementor_pro"]{color:#c60051}.elementor-plugins-gopro{color:#93003c;text-shadow:1px 1px 1px #eee;font-weight:700}#elementor-switch-mode{margin:15px 0}#elementor-editor-button,#elementor-switch-mode-button{outline:none;cursor:pointer}#elementor-editor-button i,#elementor-switch-mode-button i{margin-right:3px;font-size:125%;font-style:normal}body.elementor-editor-active .elementor-switch-mode-off{display:none}body.elementor-editor-active #elementor-switch-mode-button{background-color:#f7f7f7;color:#555;border-color:#ccc;-webkit-box-shadow:0 1px 0 #ccc!important;box-shadow:0 1px 0 #ccc!important;text-shadow:unset}body.elementor-editor-active #elementor-switch-mode-button:hover{background-color:#e9e9e9}body.elementor-editor-active #elementor-switch-mode-button:active{-webkit-box-shadow:inset 0 1px 0 #ccc;box-shadow:inset 0 1px 0 #ccc;-webkit-transform:translateY(1px);-ms-transform:translateY(1px);transform:translateY(1px)}body.elementor-editor-active #postdivrich{display:none!important}body.elementor-editor-active .block-editor-block-list__layout,body.elementor-editor-active .editor-block-list__layout,body.elementor-editor-inactive #elementor-editor,body.elementor-editor-inactive .elementor-switch-mode-on{display:none}body.elementor-editor-active .edit-post-layout__content .edit-post-visual-editor{-ms-flex-preferred-size:auto;flex-basis:auto}body.elementor-editor-active #elementor-editor{margin-bottom:50px}body.elementor-editor-active .edit-post-text-editor__body .editor-post-text-editor{display:none}body .block-editor #elementor-switch-mode{margin:0 15px}body .block-editor #elementor-switch-mode .button{margin:2px;height:33px;font-size:13px;line-height:1}body .block-editor #elementor-switch-mode .button i{padding-right:5px}.elementor-button{font-size:13px;text-decoration:none;padding:15px 40px}#elementor-editor{height:300px;width:100%;-webkit-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease}#elementor-editor .elementor-loader-wrapper{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:300px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}#elementor-editor .elementor-loader{border-radius:50%;padding:40px;height:150px;width:150px;background-color:hsla(0,0%,100%,.9);-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:2px 2px 20px 4px rgba(0,0,0,.02);box-shadow:2px 2px 20px 4px rgba(0,0,0,.02)}#elementor-editor .elementor-loader-boxes{height:100%;width:100%;position:relative}#elementor-editor .elementor-loader-box{position:absolute;background-color:#d5dadf;-webkit-animation:load 1.8s linear infinite;animation:load 1.8s linear infinite}#elementor-editor .elementor-loader-box:first-of-type{width:20%;height:100%;left:0;top:0}#elementor-editor .elementor-loader-box:not(:first-of-type){right:0;height:20%;width:60%}#elementor-editor .elementor-loader-box:nth-of-type(2){top:0;-webkit-animation-delay:calc(1.8s / 4 * -1);animation-delay:calc(1.8s / 4 * -1)}#elementor-editor .elementor-loader-box:nth-of-type(3){top:40%;-webkit-animation-delay:calc(1.8s / 4 * -2);animation-delay:calc(1.8s / 4 * -2)}#elementor-editor .elementor-loader-box:nth-of-type(4){bottom:0;-webkit-animation-delay:calc(1.8s / 4 * -3);animation-delay:calc(1.8s / 4 * -3)}#elementor-editor .elementor-loading-title{color:#a4afb7;text-align:center;text-transform:uppercase;margin-top:30px;letter-spacing:7px;text-indent:7px;font-size:10px;width:100%}#elementor-go-to-edit-page-link{height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid #ddd;background-color:#f7f7f7;text-decoration:none;position:relative;font-family:Sans-serif}#elementor-go-to-edit-page-link:hover{background-color:#fff}#elementor-go-to-edit-page-link:focus{-webkit-box-shadow:none;box-shadow:none}#elementor-go-to-edit-page-link.elementor-animate #elementor-editor-button,#elementor-go-to-edit-page-link:not(.elementor-animate) .elementor-loader-wrapper{display:none}.elementor-button-spinner:before{font:normal 20px/.5 dashicons;speak:none;display:inline-block;padding:0;top:8px;left:-4px;position:relative;vertical-align:top;content:"\f463"}.elementor-button-spinner.loading:before{-webkit-animation:rotation 1s linear infinite;animation:rotation 1s linear infinite}.elementor-button-spinner.success:before{content:"\f147";color:#46b450}.elementor-blank_state{padding:5em 0;margin:auto;max-width:520px;text-align:center;color:#6d7882;font-family:Roboto,sans-serif}.elementor-blank_state i{font-size:50px;color:#a4afb7}.elementor-blank_state h3{font-size:32px;font-weight:300;color:inherit;margin:40px 0 10px;line-height:1.2}.elementor-blank_state p{font-size:16px;font-weight:400;color:#a4afb7;margin-bottom:40px}.elementor-blank_state .elementor-button{display:inline-block}#available-widgets [class*=elementor-template] .widget-title:before{content:"\e813";font-family:eicons;font-size:17px}.elementor-settings-form-page{padding-top:30px}._elementor_settings_update_time,.elementor-settings-form-page:not(.elementor-active){display:none}#confirm_fa_migration_admin_modal .dialog-confirm-ok{color:#6d7882}body.post-type-attachment table.media .column-title .media-icon img[src$=".svg"]{width:100%}.e-major-update-warning{margin-bottom:5px;max-width:1000px;display:-webkit-box;display:-ms-flexbox;display:flex}.e-major-update-warning__separator{margin:15px -12px}.e-major-update-warning__icon{font-size:17px;margin-right:9px;margin-left:2px}.e-major-update-warning__title{font-weight:600;margin-bottom:10px}.e-major-update-warning+p{display:none}.notice-success .e-major-update-warning__separator{border:1px solid #46b450}.notice-success .e-major-update-warning__icon{color:#79ba49}.notice-warning .e-major-update-warning__separator{border:1px solid #ffb900}.notice-warning .e-major-update-warning__icon{color:#f56e28}.plugins table.e-compatibility-update-table tr{background:transparent}.plugins table.e-compatibility-update-table tr th{font-weight:600}.plugins table.e-compatibility-update-table tr td,.plugins table.e-compatibility-update-table tr th{min-width:250px;font-size:13px;background:transparent;-webkit-box-shadow:none;box-shadow:none;border:none;padding:5px 15px 5px 0}:root{--e-focus-color:rgba(0,115,170,0.4);--e-context-primary-color:#0073aa;--e-context-primary-color-dark:#005177;--e-context-primary-tint-4:rgba(0,115,170,0.4);--e-context-primary-tint-1:rgba(0,115,170,0.04);--e-context-success-color:#39b54a;--e-context-success-color-dark:#2d8e3a;--e-context-success-tint-4:rgba(57,181,74,0.4);--e-context-success-tint-1:rgba(57,181,74,0.04);--e-context-info-color:#71d7f7;--e-context-info-color-dark:#41c9f4;--e-context-info-tint-4:rgba(113,215,247,0.4);--e-context-info-tint-1:rgba(113,215,247,0.04);--e-context-warning-color:#fcb92c;--e-context-warning-color-dark:#f2a503;--e-context-warning-tint-4:rgba(252,185,44,0.4);--e-context-warning-tint-1:rgba(252,185,44,0.04);--e-context-error-color:#d72b3f;--e-context-error-color-dark:#ae2131;--e-context-error-tint-4:rgba(215,43,63,0.4);--e-context-error-tint-1:rgba(215,43,63,0.04);--e-context-cta-color:#93003c;--e-context-cta-color-dark:#600027;--e-context-cta-tint-4:rgba(147,0,60,0.4);--e-context-cta-tint-1:rgba(147,0,60,0.04)}.e-getting-started{max-width:900px;padding:2.5em 0;margin:auto;text-align:center}.e-getting-started__header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-shadow:0 0 8px rgba(0,0,0,.1);box-shadow:0 0 8px rgba(0,0,0,.1)}.e-getting-started__header .e-logo-wrapper{font-size:10px;margin-right:10px}.e-getting-started__title{padding:0 15px;font-weight:600;text-transform:uppercase;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.e-getting-started__skip{border-left:1px solid #eee;font-size:16px;color:inherit}.e-getting-started__skip i{padding:15px}.e-getting-started__content{padding:50px}.e-getting-started__content h2{font-size:2em;margin-top:0}.e-getting-started__content--narrow{max-width:500px;margin:auto}.e-getting-started__video{margin:40px 0 60px}.e-getting-started__video iframe{-webkit-box-shadow:10px 10px 20px rgba(0,0,0,.15);box-shadow:10px 10px 20px rgba(0,0,0,.15)}.e-getting-started__actions .button-primary{margin-right:20px}:root{--e-button-padding-y:0.4375rem;--e-button-padding-x:0.75rem;--e-button-font-size:0.8125rem;--e-button-font-weight:500;--e-button-line-height:0.9375rem;--e-button-border-radius:3px;--e-button-context-color:var(--e-context-primary-color);--e-button-context-color-dark:var(--e-context-primary-color-dark);--e-button-context-tint:var(--e-context-primary-tint-1)}.e-button{display:inline-block;font-weight:var(--e-button-font-weight);text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:#fff;border:0;text-decoration:none;background:var(--e-button-context-color);padding:var(--e-button-padding-y) var(--e-button-padding-x);font-size:var(--e-button-font-size);line-height:var(--e-button-line-height);border-radius:var(--e-button-border-radius);-webkit-transition:background-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;-o-transition:background-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out}.e-button:active,.e-button:focus,.e-button:hover{color:#fff;text-decoration:none;background:var(--e-button-context-color-dark)}.e-button.focus,.e-button:focus{outline:0;-webkit-box-shadow:0 0 0 2px var(--e-focus-color);box-shadow:0 0 0 2px var(--e-focus-color)}.e-button.disabled,.e-button:disabled{opacity:.5;-webkit-box-shadow:none;box-shadow:none}.e-button:not(:disabled):not(.disabled){cursor:pointer}.e-button:not(:disabled):not(.disabled).active:focus,.e-button:not(:disabled):not(.disabled):active:focus{-webkit-box-shadow:0 0 0 2px var(--e-focus-color);box-shadow:0 0 0 2px var(--e-focus-color)}.e-button--primary{--e-button-context-color:var(--e-context-primary-color);--e-button-context-color-dark:var(--e-context-primary-color-dark);--e-button-context-tint:var(--e-context-primary-tint-1);--e-focus-color:var(--e-context-primary-tint-4)}.e-button--success{--e-button-context-color:var(--e-context-success-color);--e-button-context-color-dark:var(--e-context-success-color-dark);--e-button-context-tint:var(--e-context-success-tint-1);--e-focus-color:var(--e-context-success-tint-4)}.e-button--info{--e-button-context-color:var(--e-context-info-color);--e-button-context-color-dark:var(--e-context-info-color-dark);--e-button-context-tint:var(--e-context-info-tint-1);--e-focus-color:var(--e-context-info-tint-4)}.e-button--warning{--e-button-context-color:var(--e-context-warning-color);--e-button-context-color-dark:var(--e-context-warning-color-dark);--e-button-context-tint:var(--e-context-warning-tint-1);--e-focus-color:var(--e-context-warning-tint-4)}.e-button--error{--e-button-context-color:var(--e-context-error-color);--e-button-context-color-dark:var(--e-context-error-color-dark);--e-button-context-tint:var(--e-context-error-tint-1);--e-focus-color:var(--e-context-error-tint-4)}.e-button--cta{--e-button-context-color:var(--e-context-cta-color);--e-button-context-color-dark:var(--e-context-cta-color-dark);--e-button-context-tint:var(--e-context-cta-tint-1);--e-focus-color:var(--e-context-cta-tint-4)}.e-button.e-button--outline{color:var(--e-button-context-color);background:none;border:1px solid}.e-button.e-button--outline:focus,.e-button.e-button--outline:hover{color:var(--e-button-context-color-dark);background:var(--e-button-context-tint)}.e-button.e-button--outline.disabled,.e-button.e-button--outline:disabled{color:var(--e-button-context-color-dark);background:#818a91}.e-button>i{line-height:inherit;height:var(--e-button-line-height);width:-webkit-min-content;width:-moz-min-content;width:min-content}.e-button>*+*{-webkit-margin-start:.5ch;margin-inline-start:.5ch}.e-button--link{color:var(--e-button-context-color);background-color:transparent}.e-button--link:focus,.e-button--link:hover{color:var(--e-button-context-color-dark);background:var(--e-button-context-tint)}.e-button--link.disabled,.e-button--link:disabled{color:#818a91}a.e-button.disabled,fieldset:disabled a.e-button{pointer-events:none}:root{--e-notice-bg:#fff;--e-notice-border-color:#ccd0d4;--e-notice-context-color:#93003c;--e-notice-context-tint:var(--e-context-cta-tint-1);--e-notice-box-shadow:0 1px 4px rgba(0,0,0,0.15);--e-notice-dismiss-color:#6d7882}.e-notice{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;background:var(--e-notice-bg);border:1px solid var(--e-notice-border-color);border-inline-start-width:4px;-webkit-box-shadow:var(--e-notice-box-shadow);box-shadow:var(--e-notice-box-shadow);margin:5px 20px 5px 2px}.e-notice.notice{padding:0}.e-notice:before{display:block;content:"";position:absolute;left:-4px;top:-1px;bottom:-1px;width:4px;background-color:var(--e-notice-context-color)}.e-notice--primary{--e-notice-context-color:var(--e-context-primary-color);--e-notice-context-color-dark:var(--e-context-primary-color-dark);--e-notice-context-tint:var(--e-context-primary-tint-1)}.e-notice--success{--e-notice-context-color:var(--e-context-success-color);--e-notice-context-color-dark:var(--e-context-success-color-dark);--e-notice-context-tint:var(--e-context-success-tint-1)}.e-notice--info{--e-notice-context-color:var(--e-context-info-color);--e-notice-context-color-dark:var(--e-context-info-color-dark);--e-notice-context-tint:var(--e-context-info-tint-1)}.e-notice--warning{--e-notice-context-color:var(--e-context-warning-color);--e-notice-context-color-dark:var(--e-context-warning-color-dark);--e-notice-context-tint:var(--e-context-warning-tint-1)}.e-notice--error{--e-notice-context-color:var(--e-context-error-color);--e-notice-context-color-dark:var(--e-context-error-color-dark);--e-notice-context-tint:var(--e-context-error-tint-1)}.e-notice--cta{--e-notice-context-color:var(--e-context-cta-color);--e-notice-context-color-dark:var(--e-context-cta-color-dark);--e-notice-context-tint:var(--e-context-cta-tint-1)}.e-notice--extended{--e-notice-is-extended:1}.e-notice--dismissible{padding-right:38px}.e-notice__aside{overflow:hidden;background-color:var(--e-notice-context-tint);width:calc(var(--e-notice-is-extended, 0) * 50px);text-align:center;padding-top:15px;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.e-notice__icon-wrapper{display:inline-block;font-size:.625rem;max-height:1.5rem;width:1.5rem;line-height:1.5rem;border-radius:100px;background:var(--e-notice-context-color);color:#fff;text-shadow:0 0 3px var(--e-notice-context-color-dark),0 0 1px var(--e-notice-context-color-dark),0 0 1px var(--e-notice-context-color-dark)}.e-notice__content{padding:20px}.e-notice__actions{display:-webkit-box;display:-ms-flexbox;display:flex}.e-notice__actions>*+*{-webkit-margin-start:8px;margin-inline-start:8px}.e-notice__dismiss{width:20px;height:20px;line-height:20px;font-size:.8125rem;text-align:center;background:none;display:block;position:absolute;top:0;inset-inline-end:1px;border:none;margin:0;padding:9px;cursor:pointer;font-style:normal}.e-notice__dismiss:before{font-family:eicons;display:inline-block;content:"\e87f";color:var(--e-notice-dismiss-color);width:20px;border-radius:20px;speak:none;text-align:center;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.e-notice__dismiss:active:before,.e-notice__dismiss:focus:before,.e-notice__dismiss:hover:before{font-weight:700}.e-notice__dismiss:focus:before{color:#fff;background:var(--e-notice-dismiss-color);outline:none}.e-notice__dismiss:focus{outline:none}.e-notice p{line-height:1.2;padding:0;margin:0}.e-notice p+.e-notice__actions{margin-top:1rem}.e-notice h3{font-size:1.0625rem;line-height:1.2;margin:0}.e-notice h3+p{margin-top:8px}.elementor-admin-alert{padding:15px;border-left:5px solid transparent;position:relative;font-size:12px;line-height:1.5;text-align:left}.elementor-admin-alert a{color:inherit}.elementor-admin-alert.elementor-alert-info{color:#31708f;background-color:#d9edf7;border-color:#bcdff1}.elementor-admin-alert.elementor-alert-success{color:#3c763d;background-color:#dff0d8;border-color:#cae6be}.elementor-admin-alert.elementor-alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#f9f0c3}.elementor-admin-alert.elementor-alert-danger{color:#a94442;background-color:#f2dede;border-color:#e8c4c4}#elementor-system-info{padding:15px}#elementor-system-info .elementor-system-info-section{margin-bottom:10px}#elementor-system-info .elementor-system-info-section>.elementor-system-info-report-name{padding-left:10px;border-bottom:1px solid #e1e1e1}#elementor-system-info .elementor-system-info-section .widefat{white-space:pre}#elementor-system-info .elementor-system-info-section .elementor-log-entries{white-space:pre-wrap}#elementor-system-info .elementor-system-info-section:not(.elementor-system-info-log) tbody td:first-child{width:300px}#elementor-system-info .elementor-system-info-report-name{text-transform:uppercase;font-size:14px;margin:0;line-height:2}#elementor-system-info .elementor-system-info-report-row{overflow:hidden;padding:5px 0}#elementor-system-info .elementor-system-info-report-row>*{float:left}#elementor-system-info .elementor-system-info-field-recommendation,#elementor-system-info .elementor-system-info-report-field{padding-left:10px;color:#7f7f7f}#elementor-system-info .elementor-system-info-report-fields{padding-left:20px}#elementor-system-info .elementor-system-info-plugin-name{color:#000}#elementor-system-info .elementor-system-info-plugin-properties{padding:10px}#elementor-system-info #elementor-system-info-raw-code{width:100%;height:200px}#elementor-system-info #elementor-system-info-raw-code-label{padding:5px;display:block}#elementor-system-info .elementor-warning td:first-child{border-left:3px solid #fcb92c}#elementor-system-info a.box-title-tool{font-size:80%;margin-left:15px;color:#818a91}#elementor-system-info a.box-title-tool:hover{text-decoration:underline}#elementor-system-info #elementor-usage-recalc{font-size:12px;color:#fff;background-color:#a4afb7;padding:4px 18px 5px;border-radius:3px}@-webkit-keyframes elementor-rotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes elementor-rotation{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}#elementor-deactivate-feedback-dialog-wrapper{display:none}#elementor-deactivate-feedback-modal .dialog-widget-content{width:550px}#elementor-deactivate-feedback-modal .dialog-header{padding:18px 15px;-webkit-box-shadow:0 0 8px rgba(0,0,0,.1);box-shadow:0 0 8px rgba(0,0,0,.1);text-align:left}#elementor-deactivate-feedback-modal .dialog-message{padding:30px 30px 0;text-align:left}#elementor-deactivate-feedback-modal .elementor-deactivate-feedback-dialog-input{float:left;margin:0 15px 0 0;-webkit-box-shadow:none;box-shadow:none}#elementor-deactivate-feedback-modal .elementor-deactivate-feedback-dialog-input:not(:checked)~.elementor-feedback-text{display:none}#elementor-deactivate-feedback-modal .elementor-deactivate-feedback-dialog-label{display:block;font-size:13px;color:#6d7882}#elementor-deactivate-feedback-modal .elementor-feedback-text{margin:10px 0 0 30px;padding:5px;font-size:13px;-webkit-box-shadow:none;box-shadow:none;background-color:#fff;width:92%}#elementor-deactivate-feedback-modal .dialog-buttons-wrapper{border-top:none;text-align:left;padding:20px 30px 30px;overflow:hidden}#elementor-deactivate-feedback-modal .dialog-submit{background-color:#93003c;border-radius:3px;color:#fff;line-height:1;padding:12px 20px;font-size:13px;width:180px;height:38px}#elementor-deactivate-feedback-modal .dialog-submit.elementor-loading:before{display:inline-block;content:"\f463";font:18px dashicons;-webkit-animation:elementor-rotation 2s linear infinite;animation:elementor-rotation 2s linear infinite}#elementor-deactivate-feedback-modal .dialog-skip{font-size:12px;color:#a4afb7;background:none;float:right;width:auto}#elementor-deactivate-feedback-modal[data-feedback-selected=elementor_pro] .elementor-feedback-text{color:#b01b1b;padding:0}#elementor-deactivate-feedback-modal[data-feedback-selected=elementor_pro] .dialog-submit{display:none}#elementor-deactivate-feedback-dialog-header i{color:#93003c;font-size:19px}#elementor-deactivate-feedback-dialog-header-title{font-size:15px;text-transform:uppercase;font-weight:700;padding-left:5px}#elementor-deactivate-feedback-dialog-form-caption{font-weight:700;font-size:15px;color:#495157;line-height:1.4}#elementor-deactivate-feedback-dialog-form-body{padding-top:30px}.elementor-deactivate-feedback-dialog-input-wrapper{line-height:1;overflow:hidden;margin-bottom:15px}#elementor-hidden-area{display:none}#elementor-import-template-trigger{cursor:pointer}#elementor-import-template-area{display:none;margin:50px 0 30px;text-align:center}#elementor-import-template-form{display:inline-block;margin-top:30px;padding:30px 50px;background-color:#fff;border:1px solid #e5e5e5}#elementor-import-template-title{font-size:18px;color:#555d66}.form-table:not(.elementor-maintenance-mode-is-enabled) .elementor-default-hide{display:none}.elementor-maintenance-mode-error{color:red;line-height:1.6;display:none}#tab-fontawesome4_migration.elementor-active~p.submit,#tab-import-export-kit.elementor-active~p.submit,#tab-replace_url.elementor-active~p.submit{display:none}#elementor_replace_url>div{max-width:800px}#elementor_replace_url>div input{margin-bottom:6px}#elementor_rollback>div,#elementor_rollback_pro>div{display:-webkit-box;display:-ms-flexbox;display:flex}#elementor_rollback>div input,#elementor_rollback>div select,#elementor_rollback_pro>div input,#elementor_rollback_pro>div select{margin-right:6px}.tab-import-export-kit__wrapper{margin:40px 0;max-width:700px}.tab-import-export-kit__container{background-color:#fff;font-size:16px;max-width:700px;padding:30px}.tab-import-export-kit__container:not(:first-child){margin-top:5px}.tab-import-export-kit__container p{color:#a4afb7;font-size:16px;margin:20px 0 25px}.tab-import-export-kit__info{font-size:14px}.tab-import-export-kit__container a:not(.elementor-button),.tab-import-export-kit__info a{color:#58d0f5;text-decoration:underline}.tab-import-export-kit__box{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.tab-import-export-kit__box h2{color:#6d7882;font-size:28px;font-weight:400;line-height:1;margin:0}.tab-import-export-kit__box .elementor-button.elementor-button-success{font-weight:700;padding:8px 16px;text-transform:none}#dashboard-widgets .e-dashboard-widget h3.e-heading{font-weight:600;margin-bottom:13px}#dashboard-widgets .e-dashboard-widget .e-divider_bottom{border-bottom:1px solid #eee;margin:0 -12px;padding:6px 12px}#dashboard-widgets .e-dashboard-widget .e-divider_top{border-top:1px solid #eee;margin:0 -12px;padding:6px 12px}#dashboard-widgets .e-dashboard-widget .e-news-feed-wrap .e-divider_top,#dashboard-widgets .e-dashboard-widget .e-quick-actions-wrap .e-divider_top{padding-top:18px;margin-top:18px}.e-dashboard-widget .dashicons{color:#606a73}.e-dashboard-widget ul.e-action-list li{margin-top:14px}.e-dashboard-widget ul.e-action-list li a{margin-left:5px}#e-dashboard-overview .dashicons{vertical-align:middle;font-size:17px}#e-dashboard-overview .e-overview__header{display:table;width:100%;-webkit-box-shadow:0 5px 8px rgba(0,0,0,.05);box-shadow:0 5px 8px rgba(0,0,0,.05);margin:0 -12px 8px;padding:0 12px 12px}#e-dashboard-overview .e-overview__create,#e-dashboard-overview .e-overview__logo,#e-dashboard-overview .e-overview__versions{display:table-cell;vertical-align:middle}#e-dashboard-overview .e-overview__logo{width:30px}#e-dashboard-overview .e-overview__versions{padding:0 10px;font-size:.9em;line-height:1.5}#e-dashboard-overview .e-overview__version{display:block}#e-dashboard-overview .e-overview__create{text-align:right}#e-dashboard-overview .e-overview__feed{font-size:14px;font-weight:500}#e-dashboard-overview .e-overview__post{margin-top:10px}#e-dashboard-overview .e-overview__post-link{display:inline-block}#e-dashboard-overview .e-overview__badge{background:#39b54a;color:#fff;font-size:.75em;padding:3px 6px;border-radius:3px;text-transform:uppercase}#e-dashboard-overview .e-overview__post-description{margin:0 0 1.5em}#e-dashboard-overview .e-overview__recently-edited li{color:#72777c}#e-dashboard-overview .e-overview__footer.e-divider_top{padding-top:12px;padding-bottom:0}#e-dashboard-overview .e-overview__footer ul{display:-webkit-box;display:-ms-flexbox;display:flex;list-style:none;margin:0;padding:0}#e-dashboard-overview .e-overview__footer ul li{padding:0 10px;margin:0;border-left:1px solid #ddd}#e-dashboard-overview .e-overview__footer ul li:first-child{padding-left:0;border:none}#e-dashboard-overview .e-overview__go-pro a{color:#93003c;font-weight:500}.post-type-elementor_library #elementor-template-library-tabs-wrapper{padding-top:2em;margin-bottom:2em}.post-type-elementor_library th#taxonomy-elementor_library_category{width:110px}#elementor-new-template-modal .dialog-message{max-height:70vh}#elementor-new-template-modal .e-hidden{display:none!important}#elementor-new-template-dialog-content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:100%;text-align:left;color:#6d7882}@media (max-width:1439px){#elementor-new-template-dialog-content{padding:0 50px}}@media (min-width:1440px){#elementor-new-template-dialog-content{padding:0 120px}}#elementor-new-template__description{width:35%;max-width:300px;padding-right:100px}#elementor-new-template__description__title{font-size:30px;color:#556068}#elementor-new-template__description__title span{font-weight:700}#elementor-new-template__description__content{font-size:16px;padding:30px 0}#elementor-new-template__take_a_tour{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:15px}#elementor-new-template__take_a_tour i{color:#93003c;font-size:30px}#elementor-new-template__take_a_tour a{color:#6d7882;padding-left:10px;text-decoration:none;font-weight:500}#elementor-new-template__form{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:440px;padding:55px;background-color:#fff;border-radius:3px;-webkit-box-shadow:0 2px 30px 0 rgba(0,0,0,.08);box-shadow:0 2px 30px 0 rgba(0,0,0,.08)}#elementor-new-template__form__title{font-size:23px;color:#556068}#elementor-new-template__form__template-type.elementor-form-field__select{max-width:none}#elementor-new-template__form__template-type-badge{position:absolute;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:2px;border-radius:2px;background-color:#f1f3f5;padding:4px;font-size:8px;font-weight:500;line-height:1;text-transform:uppercase;top:50%;inset-inline-end:28px;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}#elementor-new-template__form .elementor-form-field__label{display:block;margin:25px 0 7px;font-size:14px;line-height:1}#elementor-new-template__form .elementor-form-field input,#elementor-new-template__form .elementor-form-field select{width:100%;height:50px;padding:10px;font-size:14px;-webkit-box-shadow:none;box-shadow:none;border-radius:3px;background:none;color:#495157;border:1px solid;outline:none}#elementor-new-template__form .elementor-form-field input:not(:focus),#elementor-new-template__form .elementor-form-field select:not(:focus){border-color:#d5dadf}#elementor-new-template__form .elementor-form-field input:focus,#elementor-new-template__form .elementor-form-field select:focus{border-color:#a4afb7}#elementor-new-template__form .elementor-form-field__select{appearance:none;-webkit-appearance:none;-moz-appearance:none;cursor:pointer}#elementor-new-template__form .elementor-form-field__select__wrapper{position:relative}#elementor-new-template__form .elementor-form-field__select__wrapper:after{font-family:eicons;content:"\e8ad";position:absolute;top:50%;right:10px;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}#elementor-new-template__form__lock_button,#elementor-new-template__form__submit{display:block;width:100%;height:50px;margin-top:24px;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;text-transform:none}@media (max-width:1024px){#elementor-new-template__description{max-width:250px;padding-right:30px}}@media (max-width:767px){#elementor-new-template__description{display:none}}#elementor-role-manager{max-width:500px;margin-top:50px}#elementor-role-manager h3{color:#6d7882;font-weight:400;font-size:22px}#elementor-role-manager .elementor-settings-form-page{padding:0}#elementor-role-manager .elementor-role-row{background:#fff;color:#6d7882;margin-bottom:2px}#elementor-role-manager .elementor-role-row .elementor-role-label{display:-webkit-box;display:-ms-flexbox;display:flex;padding:15px 20px;font-weight:500;cursor:pointer}#elementor-role-manager .elementor-role-row .elementor-role-label span.elementor-role-name{padding-right:20px}#elementor-role-manager .elementor-role-row .elementor-role-label span.elementor-role-toggle{text-align:right;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}#elementor-role-manager .elementor-role-row .elementor-role-label span.elementor-role-excluded-indicator{color:#a4afb7}#elementor-role-manager .elementor-role-row .elementor-role-controls{background-color:#f7f7f7;padding:20px 20px 5px}#elementor-role-manager .elementor-role-row .elementor-role-controls>div{margin-bottom:15px}#elementor-role-manager .elementor-role-row .elementor-role-controls .elementor-role-go-pro{display:-webkit-box;display:-ms-flexbox;display:flex}#elementor-role-manager .elementor-role-row .elementor-role-controls .elementor-role-go-pro .elementor-role-go-pro__desc{font-weight:500;font-style:italic}#elementor-role-manager .elementor-role-row .elementor-role-controls .elementor-role-go-pro .elementor-role-go-pro__link{text-align:right;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area{color:#c2cbd2;cursor:pointer}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area:hover .elementor-beta-tester-do-not-show-again,#elementor-beta-tester-modal .elementor-templates-modal__header__items-area:hover .elementor-templates-modal__header__item>i{color:#6d7882}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area .elementor-templates-modal__header__close{border:none}#elementor-beta-tester-modal .elementor-templates-modal__header__items-area .elementor-beta-tester-do-not-show-again{text-transform:uppercase;font-weight:700;font-size:12px;-webkit-transition:all .3s;-o-transition:all .3s;transition:all .3s}#elementor-beta-tester-modal .dialog-lightbox-widget-content{max-width:500px;height:auto}#elementor-beta-tester-modal .dialog-lightbox-message{padding:40px;height:300px;background-color:#fff}#elementor-beta-tester-form__caption{font-weight:700;font-size:20px;color:#495157}#elementor-beta-tester-form__description{font-size:15px;color:#6d7882;margin-top:10px}#elementor-beta-tester-form__input-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;margin-top:30px}#elementor-beta-tester-form__input-wrapper .elementor-button{border-radius:0 3px 3px 0}#elementor-beta-tester-form__email{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;border:1px solid #d5dadf;border-right:0;border-radius:3px 0 0 3px;margin:0;padding:10px;height:50px}#elementor-beta-tester-form__terms{margin-top:40px;font-size:11px;color:#a4afb7}.e-experiment__title{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.e-experiment__title__indicator{position:absolute;height:10px;width:10px;border-radius:50%;border:2px solid #fff;-webkit-box-shadow:0 2px 4px rgba(0,0,0,.1);box-shadow:0 2px 4px rgba(0,0,0,.1);-ms-flex-negative:0;flex-shrink:0;margin-top:2px}.e-experiment__title__indicator--active{background:#39b54a}.e-experiment__title__label{margin-left:24px}.e-experiment__title__tag{background:#0085ba;color:#fff;font-size:.8em;padding:3px 6px;line-height:1;border-radius:3px;font-weight:600;margin-top:5px;margin-left:24px}.e-experiment__table-title{margin:30px 0}.e-experiment__dependency,.e-experiment__status{margin-top:4px;font-size:.9em;line-height:18px;font-weight:700;font-style:italic}.e-experiment__button.button{margin:18px 14px 22px 0}.e-experiment__dependency{color:#21759b}.e-experiment__dependency__title{font-weight:inherit}#tab-experiments .form-table tr{border-bottom:1px solid #dcdcde}#tab-experiments .form-table tr:last-child{border-bottom:none}#tab-experiments .form-table tr .description{font-size:.9em;margin:10px 0;max-width:820px}.e-landing-pages-empty .elementor-blank_state{padding:5em 0 2em}.e-landing-pages-empty .e-trashed-items{text-align:center}
assets/css/app-base-rtl.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
  :root {
3
  --color-box-shadow-color: rgba(0, 0, 0, 0.05);
4
  }
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
  :root {
3
  --color-box-shadow-color: rgba(0, 0, 0, 0.05);
4
  }
assets/css/app-base-rtl.min.css CHANGED
@@ -1,2 +1,2 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
  :root{--color-box-shadow-color:rgba(0,0,0,0.05)}.eps-theme-dark{--color-box-shadow-color:rgba(0,0,0,0.1)}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media screen and (min-width:480px){.text-start-sm{text-align:start}}@media screen and (min-width:480px){.text-center-sm{text-align:center}}@media screen and (min-width:480px){.text-end-sm{text-align:end}}@media screen and (min-width:768px){.text-start-md{text-align:start}}@media screen and (min-width:768px){.text-center-md{text-align:center}}@media screen and (min-width:768px){.text-end-md{text-align:end}}@media screen and (min-width:1025px){.text-start-lg{text-align:start}}@media screen and (min-width:1025px){.text-center-lg{text-align:center}}@media screen and (min-width:1025px){.text-end-lg{text-align:end}}@media screen and (min-width:1440px){.text-start-xl{text-align:start}}@media screen and (min-width:1440px){.text-center-xl{text-align:center}}@media screen and (min-width:1440px){.text-end-xl{text-align:end}}@media screen and (min-width:1600px){.text-start-xxl{text-align:start}}@media screen and (min-width:1600px){.text-center-xxl{text-align:center}}@media screen and (min-width:1600px){.text-end-xxl{text-align:end}}@-webkit-keyframes eps-animation-pop{0%{-webkit-transform:scale(.75);transform:scale(.75);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes eps-animation-pop{0%{-webkit-transform:scale(.75);transform:scale(.75);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
  :root{--color-box-shadow-color:rgba(0,0,0,0.05)}.eps-theme-dark{--color-box-shadow-color:rgba(0,0,0,0.1)}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media screen and (min-width:480px){.text-start-sm{text-align:start}}@media screen and (min-width:480px){.text-center-sm{text-align:center}}@media screen and (min-width:480px){.text-end-sm{text-align:end}}@media screen and (min-width:768px){.text-start-md{text-align:start}}@media screen and (min-width:768px){.text-center-md{text-align:center}}@media screen and (min-width:768px){.text-end-md{text-align:end}}@media screen and (min-width:1025px){.text-start-lg{text-align:start}}@media screen and (min-width:1025px){.text-center-lg{text-align:center}}@media screen and (min-width:1025px){.text-end-lg{text-align:end}}@media screen and (min-width:1440px){.text-start-xl{text-align:start}}@media screen and (min-width:1440px){.text-center-xl{text-align:center}}@media screen and (min-width:1440px){.text-end-xl{text-align:end}}@media screen and (min-width:1600px){.text-start-xxl{text-align:start}}@media screen and (min-width:1600px){.text-center-xxl{text-align:center}}@media screen and (min-width:1600px){.text-end-xxl{text-align:end}}@-webkit-keyframes eps-animation-pop{0%{-webkit-transform:scale(.75);transform:scale(.75);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes eps-animation-pop{0%{-webkit-transform:scale(.75);transform:scale(.75);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}
assets/css/app-base.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
  :root {
3
  --color-box-shadow-color: rgba(0, 0, 0, 0.05);
4
  }
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
  :root {
3
  --color-box-shadow-color: rgba(0, 0, 0, 0.05);
4
  }
assets/css/app-base.min.css CHANGED
@@ -1,2 +1,2 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
  :root{--color-box-shadow-color:rgba(0,0,0,0.05)}.eps-theme-dark{--color-box-shadow-color:rgba(0,0,0,0.1)}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media screen and (min-width:480px){.text-start-sm{text-align:start}}@media screen and (min-width:480px){.text-center-sm{text-align:center}}@media screen and (min-width:480px){.text-end-sm{text-align:end}}@media screen and (min-width:768px){.text-start-md{text-align:start}}@media screen and (min-width:768px){.text-center-md{text-align:center}}@media screen and (min-width:768px){.text-end-md{text-align:end}}@media screen and (min-width:1025px){.text-start-lg{text-align:start}}@media screen and (min-width:1025px){.text-center-lg{text-align:center}}@media screen and (min-width:1025px){.text-end-lg{text-align:end}}@media screen and (min-width:1440px){.text-start-xl{text-align:start}}@media screen and (min-width:1440px){.text-center-xl{text-align:center}}@media screen and (min-width:1440px){.text-end-xl{text-align:end}}@media screen and (min-width:1600px){.text-start-xxl{text-align:start}}@media screen and (min-width:1600px){.text-center-xxl{text-align:center}}@media screen and (min-width:1600px){.text-end-xxl{text-align:end}}@-webkit-keyframes eps-animation-pop{0%{-webkit-transform:scale(.75);transform:scale(.75);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes eps-animation-pop{0%{-webkit-transform:scale(.75);transform:scale(.75);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
  :root{--color-box-shadow-color:rgba(0,0,0,0.05)}.eps-theme-dark{--color-box-shadow-color:rgba(0,0,0,0.1)}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media screen and (min-width:480px){.text-start-sm{text-align:start}}@media screen and (min-width:480px){.text-center-sm{text-align:center}}@media screen and (min-width:480px){.text-end-sm{text-align:end}}@media screen and (min-width:768px){.text-start-md{text-align:start}}@media screen and (min-width:768px){.text-center-md{text-align:center}}@media screen and (min-width:768px){.text-end-md{text-align:end}}@media screen and (min-width:1025px){.text-start-lg{text-align:start}}@media screen and (min-width:1025px){.text-center-lg{text-align:center}}@media screen and (min-width:1025px){.text-end-lg{text-align:end}}@media screen and (min-width:1440px){.text-start-xl{text-align:start}}@media screen and (min-width:1440px){.text-center-xl{text-align:center}}@media screen and (min-width:1440px){.text-end-xl{text-align:end}}@media screen and (min-width:1600px){.text-start-xxl{text-align:start}}@media screen and (min-width:1600px){.text-center-xxl{text-align:center}}@media screen and (min-width:1600px){.text-end-xxl{text-align:end}}@-webkit-keyframes eps-animation-pop{0%{-webkit-transform:scale(.75);transform:scale(.75);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes eps-animation-pop{0%{-webkit-transform:scale(.75);transform:scale(.75);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}
assets/css/app-rtl.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
  @import "//fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap";
3
  @import "//fonts.googleapis.com/css2?family=DM%20Sans&display=swap";
4
  @import "//fonts.googleapis.com/css2?family=Source%20Serif%20Pro&display=swap";
@@ -123,7 +123,7 @@
123
  }
124
  /**
125
  TODO: The molecules, atoms and such generics should be at top level, so the styles will be not depended on the order.
126
- EG: '../../../../core/app/assets/styles/generic'.
127
  Auto-import is designed for CSS that not dependent on the loading order.
128
  */
129
  .eps-button {
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
  @import "//fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap";
3
  @import "//fonts.googleapis.com/css2?family=DM%20Sans&display=swap";
4
  @import "//fonts.googleapis.com/css2?family=Source%20Serif%20Pro&display=swap";
123
  }
124
  /**
125
  TODO: The molecules, atoms and such generics should be at top level, so the styles will be not depended on the order.
126
+ EG: '../../../app/assets/styles/generic'.
127
  Auto-import is designed for CSS that not dependent on the loading order.
128
  */
129
  .eps-button {
assets/css/app-rtl.min.css CHANGED
@@ -1,2 +1,2 @@
1
- /*! elementor - v3.7.8 - 02-10-2022 */
2
  @import "//fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap";@import "//fonts.googleapis.com/css2?family=DM%20Sans&display=swap";@import "//fonts.googleapis.com/css2?family=Source%20Serif%20Pro&display=swap";.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media screen and (min-width:480px){.text-start-sm{text-align:start}}@media screen and (min-width:480px){.text-center-sm{text-align:center}}@media screen and (min-width:480px){.text-end-sm{text-align:end}}@media screen and (min-width:768px){.text-start-md{text-align:start}}@media screen and (min-width:768px){.text-center-md{text-align:center}}@media screen and (min-width:768px){.text-end-md{text-align:end}}@media screen and (min-width:1025px){.text-start-lg{text-align:start}}@media screen and (min-width:1025px){.text-center-lg{text-align:center}}@media screen and (min-width:1025px){.text-end-lg{text-align:end}}@media screen and (min-width:1440px){.text-start-xl{text-align:start}}@media screen and (min-width:1440px){.text-center-xl{text-align:center}}@media screen and (min-width:1440px){.text-end-xl{text-align:end}}@media screen and (min-width:1600px){.text-start-xxl{text-align:start}}@media screen and (min-width:1600px){.text-center-xxl{text-align:center}}@media screen and (min-width:1600px){.text-end-xxl{text-align:end}}@-webkit-keyframes eps-animation-pop{0%{-webkit-transform:scale(.75);transform:scale(.75);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes eps-animation-pop{0%{-webkit-transform:scale(.75);transform:scale(.75);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}.eps-button{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;--button-line-height:16px;--button-padding-y:0.5em;--button-padding-x:1.5em;--button-primary-background-color:#39b54a;--button-primary-hover-background-color:#33a242;--button-primary-active-background-color:#35a945;--button-primary-color:#fff;--button-secondary-background-color:#a4afb7;--button-secondary-hover-background-color:#96a2ac;--button-secondary-active-background-color:#9ba7b0;--button-secondary-color:#fff;--button-danger-background-color:#b01b1b;--button-danger-hover-background-color:#9a1818;--button-danger-active-background-color:#a31919;--button-danger-color:#fff;--button-cta-background-color:#93003f;--button-cta-hover-background-color:#7a0034;--button-cta-active-background-color:#840038;--button-cta-color:#fff;--button-link-background-color:#58d0f5;--button-link-hover-background-color:#40c9f4;--button-link-active-background-color:#4accf4;--button-link-color:#fff;--button-disabled-background-color:#c2cbd2;--button-disabled-hover-background-color:#b3bec7;--button-disabled-active-background-color:#b9c3cc;--button-disabled-color:#fff;color:var(--button-background-color,currentColor);font-size:var(--button-font-size,inherit);font-weight:500;line-height:var(--button-line-height);cursor:pointer}.eps-button:active{--button-background-color:var(--button-active-background-color,transparent)}.eps-button:hover{--button-background-color:var(--button-hover-background-color)}.eps-theme-dark .eps-button{--button-primary-background-color:#39b54a;--button-primary-color:#fff;--button-primary-hover-background-color:#33a242;--button-primary-active-background-color:#35a945;--button-secondary-background-color:#b4b5b7;--button-secondary-color:#fff;--button-secondary-hover-background-color:#a7a8ab;--button-secondary-active-background-color:#acadb0;--button-cta-background-color:#93003f;--button-cta-hover-background-color:#7a0034;--button-cta-active-background-color:#840038;--button-cta-color:#fff;--button-link-background-color:#58d0f5;--button-link-hover-background-color:#40c9f4;--button-link-active-background-color:#4accf4;--button-link-color:#fff;--button-disabled-background-color:#64666a;--button-disabled-hover-background-color:#58595d;--button-disabled-active-background-color:#5d5e62;--button-disabled-color:#fff}.eps-button--contained{padding:var(--button-padding-y) var(--button-padding-x);background-color:var(--button-background-color,transparent);border:1px solid var(--button-background-color)}.eps-button--contained,.eps-button--contained:hover{color:var(--button-color)}.eps-button--outlined{display:block;padding:var(--button-padding-y) var(--button-padding-x);border:1px solid var(--button-background-color)}.eps-button--contained,.eps-button--outlined{border-radius:.1875rem}.eps-button--underlined{text-decoration:underline}.eps-button--sm{--button-font-size:0.75rem;--button-line-height:14px}.eps-button--lg{--button-font-size:0.9375rem;--button-line-height:18px}.eps-button--primary{--button-color:var(--button-primary-color);--button-background-color:var(--button-primary-background-color);--button-hover-background-color:var(--button-primary-hover-background-color);--button-active-background-color:var(--button-primary-active-background-color)}.eps-button--secondary{--button-color:var(--button-secondary-color);--button-background-color:var(--button-secondary-background-color);--button-hover-background-color:var(--button-secondary-hover-background-color);--button-active-background-color:var(--button-secondary-active-background-color)}.eps-button--danger{--button-color:var(--button-danger-color);--button-background-color:var(--button-danger-background-color);--button-hover-background-color:var(--button-danger-hover-background-color);--button-active-background-color:var(--button-danger-active-background-color)}.eps-button--cta{--button-color:var(--button-cta-color);--button-background-color:var(--button-cta-background-color);--button-hover-background-color:var(--button-cta-hover-background-color);--button-active-background-color:var(--button-cta-active-background-color)}.eps-button--link{--button-color:var(--button-link-color);--button-background-color:var(--button-link-background-color);--button-hover-background-color:var(--button-link-hover-background-color);--button-active-background-color:var(--button-link-active-background-color)}.eps-button--disabled,.eps-button[disabled]{--button-color:var(--button-disabled-color);--button-background-color:var(--button-disabled-background-color);--button-hover-background-color:var(--button-disabled-hover-background-color);--button-active-background-color:var(--button-disabled-active-background-color);cursor:default}:root{--app-background-color:#f1f3f5;--app-box-shadow-color:rgba(var(--box-shadow-color,rgba(0,0,0,0.15)),0.2);--app-header-background-color:#fff;--app-header-color:#495157;--app-sidebar-background-color:hsla(0,0%,100%,0.5);--app-header-buttons-separator-color:#d5dadf;--app-header-buttons-color:#a4afb7;--app-lightbox-background-color:rgba(0,0,0,0.8)}.eps-theme-dark{--app-background-color:#34383c;--app-box-shadow-color:rgba(var(--box-shadow-color,rgba(0,0,0,0.15)),0.2);--app-header-background-color:#26292c;--app-header-color:#e0e1e3;--app-sidebar-background-color:#34383c;--app-header-buttons-separator-color:#64666a;--app-header-buttons-color:#b4b5b7;--app-lightbox-background-color:rgba(0,0,0,0.8)}:root{--text-muted:#d5dadf;--disabled:#c2cbd2;--danger:#b01b1b;--body-color:#6d7882;--hr-color:#d5dadf;--box-shadow-color:theme-colors(dark);--display-1-color:#495157;--display-2-color:#495157;--display-3-color:#6d7882;--display-4-color:#495157;--h1-color:#6d7882;--h2-color:#6d7882;--h3-color:#495157;--h4-color:#495157;--h5-color:#495157;--h6-color:#495157;--text-base-color:#495157;--text-xl-color:#495157;--text-lg-color:#495157;--text-sm-color:#495157;--text-xs-color:#495157;--text-xxs-color:#495157;--gray-800:#495157;--gray-700:#556068;--gray-600:#6d7882;--gray-500:#a4afb7;--gray-400:#c2cbd2;--gray-300:#d5dadf;--gray-200:#f1f3f5;--gray-100:#fcfcfc}.eps-theme-dark,:root{--light:#fff;--dark:#000;--info:#58d0f5;--cta:#93003f;--success:#39b54a;--warning:#fcb92c;--link-color:#58d0f5;--link-hover-color:#10bcf1}.eps-theme-dark{--text-muted:#7d7e82;--disabled:#64666a;--accent:#58d0f5;--danger:#f84343;--body-color:#e0e1e3;--body-bg:#fff;--hr-color:#4c4f56;--box-shadow-color:rgba(0,0,0,0.15);--display-1-color:#e0e1e3;--display-2-color:#e0e1e3;--display-3-color:#e0e1e3;--display-4-color:#e0e1e3;--h1-color:#e0e1e3;--h2-color:#e0e1e3;--h3-color:#e0e1e3;--h4-color:#e0e1e3;--h5-color:#e0e1e3;--h6-color:#e0e1e3;--text-base-color:#b4b5b7;--text-xl-color:#b4b5b7;--text-lg-color:#b4b5b7;--text-sm-color:#b4b5b7;--text-xs-color:#b4b5b7;--text-xxs-color:#b4b5b7;--gray-800:#26292c;--gray-700:#34383c;--gray-600:#404349;--gray-500:#4c4f56;--gray-400:#64666a;--gray-300:#7d7e82;--gray-200:#b4b5b7;--gray-100:#e0e1e3}*,:after,:before{-webkit-box-sizing:border-box;box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-size:.875rem;font-weight:400;line-height:1.5;color:var(--body-color);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:var(--body-bg)}::-moz-selection{background-color:rgba(147,0,63,.5)}::selection{background-color:rgba(147,0,63,.5)}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}h1,h2,h3,h4,h5,h6{font-size:100%;margin:0;padding:0;line-height:inherit;font-weight:400}p{margin-top:0}b,strong{font-weight:700}small{font-size:80%}a{--eps-link-color:$eps-link-color;color:var(--eps-link-color);background-color:transparent}a,a:active,a:focus,a:hover{text-decoration:none}a:focus,a:hover{--eps-link-color:$eps-link-hover-color}a:not([href]),a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:monospace;font-size:1em}figure{margin:0}img{border-style:none}img,svg{vertical-align:middle}svg{overflow:hidden}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}[hidden]{display:none!important}hr{border:0;border-bottom:1px solid var(--hr-color)}.eps-display-1{color:var(--display-1-color)}.eps-display-1,.eps-display-2{font-size:1.85rem;margin-top:.5rem;margin-bottom:.5rem}.eps-display-2{color:var(--display-2-color)}.eps-display-3{font-size:1.85rem;color:var(--display-3-color);margin-top:0;margin-bottom:1.25rem}.eps-display-4{font-size:1.85rem;color:var(--display-4-color);margin-top:.5rem;margin-bottom:.5rem}.eps-h1,h1{font-size:1.625rem;line-height:1;color:var(--h1-color);margin-bottom:1.25rem;font-weight:500}.eps-h2,h2{font-size:1.25rem;color:var(--h2-color);margin-bottom:1.25rem}.eps-h2,.eps-h3,h2,h3{line-height:1.2;margin-top:0;font-weight:500}.eps-h3,h3{font-size:1rem;color:var(--h3-color);margin-bottom:.5rem}.eps-h4,h4{font-size:.9375rem;color:var(--h4-color)}.eps-h4,.eps-h5,h4,h5{margin-top:0;margin-bottom:.5rem}.eps-h5,h5{font-size:.875rem;color:var(--h5-color)}.eps-h6,h6{font-size:.875rem;color:var(--h-6-color);margin-top:0;margin-bottom:.5rem;font-weight:700}.eps-text-xxs{line-height:1.2;color:var(--text-xxs-color)}.eps-text-xs,.eps-text-xxs{font-size:.75rem;font-weight:400}.eps-text-xs{line-height:1.5;color:var(--text-xs-color)}.eps-text{font-size:.875rem;color:var(--text-base-color)}.eps-text,.eps-text-sm{line-height:1.5;font-weight:400}.eps-text-sm{font-size:.8125rem;color:var(--text-sm-color)}.eps-text-lg{font-size:.9375rem;color:var(--text-lg-color)}.eps-text-lg,.eps-text-xl{line-height:1.5;font-weight:400}.eps-text-xl{font-size:1rem;color:var(--text-xl-color)}.video-wrapper{position:relative;padding-bottom:56.25%;height:0}.video-wrapper iframe{position:absolute;top:0;left:0;width:100%;height:100%}.eps-separator{margin-bottom:2.75rem}.eps-theme-dark{--e-app-back-button-color:#b4b5b7}.back-button,.e-app-back-button{--button-background-color:var(--e-app-back-button-color,#a4afb7);margin-bottom:1.5rem}.back-button .eps-icon,.e-app-back-button .eps-icon{-webkit-margin-end:.3125rem;margin-inline-end:.3125rem}.eps-theme-dark{--input-border-color:--hr-color}.eps-input{border:1px solid var(--hr-color);border-radius:.1875rem;background:transparent;color:inherit;height:1.875rem;padding:0 .3125rem}.eps-input--block{width:100%}.eps-app{height:100vh;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;color:var(--body-color);background-color:var(--app-background-color);position:absolute;border-radius:0;-webkit-box-shadow:2px 8px 23px 3px var(--color-box-shadow-color);box-shadow:2px 8px 23px 3px var(--color-box-shadow-color);overflow:hidden;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;max-width:100%}.eps-app,.eps-app__lightbox{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%}.eps-app__lightbox{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;position:fixed;height:100%;background-color:var(--app-lightbox-background-color);z-index:1040;bottom:0;left:0}.eps-app__header{-ms-flex-negative:0;flex-shrink:0;font-size:.9375rem;color:var(--app-header-color);background-color:var(--app-header-background-color);-webkit-box-shadow:0 4px 10px var(--color-box-shadow-color);box-shadow:0 4px 10px var(--color-box-shadow-color);position:relative;z-index:3;height:3.125rem;padding:0 1rem}.eps-app__header-buttons{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;font-size:1.125rem}.eps-app__header-btn{--button-background-color:var(--app-header-buttons-color);-webkit-padding-start:1rem;padding-inline-start:1rem;font-size:1.125rem;line-height:1.25rem}.eps-app__header-btn:first-child{-webkit-border-start:1px solid var(--app-header-buttons-separator-color);border-inline-start:1px solid var(--app-header-buttons-separator-color)}.eps-app__header-btn:not(:first-child){-webkit-padding-end:1rem;padding-inline-end:1rem}.eps-app__logo-title-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.eps-app__logo{display:block;width:1.75rem;height:1.75rem;line-height:1.75rem;text-align:center;font-size:calc(.4 * 1.75rem);border-radius:50%;color:#fff;background-color:#93003f}.eps-app__logo:not(:last-child){-webkit-margin-end:.625rem;margin-inline-end:.625rem}.eps-app__title{font-size:.9375rem;font-weight:700;text-transform:uppercase;margin-bottom:0}.eps-app__main{display:-webkit-box;display:-ms-flexbox;display:flex;overflow:hidden;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.eps-app__sidebar{background-color:var(--app-sidebar-background-color);padding-top:1.25rem;width:30%;max-width:17.1875rem;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;overflow-y:auto;-webkit-box-shadow:2px 8px 23px 3px var(--color-box-shadow-color);box-shadow:2px 8px 23px 3px var(--color-box-shadow-color);z-index:4}.eps-app__content{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;position:relative;padding:2.75rem;height:100%;overflow-y:auto}.e-app-upload-file__input{display:none}.e-app-drop-zone{--e-app-drop-zone-icon-color:#c2cbd2;--e-app-drop-zone-text-color:#a4afb7;--e-app-drop-zone-secondary-text-color:#6d7882}.e-app-drop-zone__icon{margin-bottom:2.75rem;color:var(--e-app-drop-zone-icon-color);font-size:60px}.e-app-drop-zone__text{color:var(--e-app-drop-zone-text-color)}.e-app-drop-zone__secondary-text{color:var(--e-app-drop-zone-secondary-text-color)}.eps-theme-dark .e-app-drop-zone{--e-app-drop-zone-icon-color:#c2cbd2;--e-app-drop-zone-text-color:#b4b5b7;--e-app-drop-zone-secondary-text-color:#e0e1e3}:root{--info-toggle-color:#d5dadf;--info-toggle-hover-color:#a4afb7}.eps-theme-dark{--placeholder-filter:invert(0.8) sepia(1) saturate(0.2) hue-rotate(180deg) contrast(1.25) brightness(1.2);--info-toggle-color:#64666a;--info-toggle-hover-color:#b4b5b7}.e-site-part .eps-card__image{-webkit-filter:var(--placeholder-filter,none);filter:var(--placeholder-filter,none)}.e-site-part__info-toggle{color:var(--info-toggle-color)}.e-site-part__info-toggle:hover{--info-toggle-color:var(--info-toggle-hover-color)}.e-site-editor__header{margin-bottom:2.75rem;border-bottom:1px solid var(--hr-color)}:root{--e-elementor-loader-container-background-color:#f1f3f5;--e-elementor-loader-background-color:hsla(0,0%,100%,0.9)}.eps-theme-dark{--e-elementor-loader-container-background-color:#34383c;--e-elementor-loader-background-color:#4c4f56}.elementor-loading{background-color:var(--e-elementor-loader-container-background-color);height:100vh}.elementor-loader-wrapper{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:300px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.elementor-loader{border-radius:50%;padding:40px;height:150px;width:150px;background-color:var(--e-elementor-loader-background-color);-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:2px 2px 20px 4px rgba(0,0,0,.02);box-shadow:2px 2px 20px 4px rgba(0,0,0,.02)}.elementor-loader-boxes{height:100%;width:100%;position:relative}.elementor-loader-box{position:absolute;background-color:#d5dadf;-webkit-animation:load 1.8s linear infinite;animation:load 1.8s linear infinite}.elementor-loader-box:first-of-type{width:20%;height:100%;left:0;top:0}.elementor-loader-box:not(:first-of-type){right:0;height:20%;width:60%}.elementor-loader-box:nth-of-type(2){top:0;-webkit-animation-delay:calc(1.8s / 4 * -1);animation-delay:calc(1.8s / 4 * -1)}.elementor-loader-box:nth-of-type(3){top:40%;-webkit-animation-delay:calc(1.8s / 4 * -2);animation-delay:calc(1.8s / 4 * -2)}.elementor-loader-box:nth-of-type(4){bottom:0;-webkit-animation-delay:calc(1.8s / 4 * -3);animation-delay:calc(1.8s / 4 * -3)}.elementor-loading-title{color:#a4afb7;text-align:center;text-transform:uppercase;margin-top:30px;letter-spacing:7px;text-indent:7px;font-size:10px;width:100%}@-webkit-keyframes load{0%{opacity:.3}50%{opacity:1}to{opacity:.3}}@keyframes load{0%{opacity:.3}50%{opacity:1}to{opacity:.3}}.eps-menu__title{margin-top:2.75rem;margin-bottom:1rem}.e-app-import{--e-app-import-back-to-library-color:#a4afb7;padding-bottom:1.25rem}.e-app-import__drop-zone{margin-top:1.25rem}.e-app-import__back-to-library{color:var(--e-app-import-back-to-library-color);margin-bottom:1.5rem}.e-app-import__back-to-library>i{-webkit-margin-end:.5rem;margin-inline-end:.5rem}.eps-theme-dark .e-app-import{--e-app-import-back-to-library-color:#b4b5b7}.e-site-editor__promotion-overlay__link{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%;height:100%;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;text-decoration:none}.e-site-editor__promotion-overlay__icon{font-size:1.25rem;color:#fff;margin-bottom:1rem}.e-app-import-export-wizard-step{--e-app-import-export-wizard-step-icon-color:#c2cbd2;--e-app-import-export-wizard-step-text-color:#a4afb7;--e-app-import-export-wizard-step-bottom-text-color:#a4afb7;height:100%;position:relative;text-align:center}.e-app-import-export-wizard-step__media-container{height:140px;margin:5.5rem 0 2.75rem}.e-app-import-export-wizard-step__icon{color:var(--e-app-import-export-wizard-step-icon-color);font-size:50px}.e-app-import-export-wizard-step__icon.eicon-loading{font-size:1.85rem}.e-app-import-export-wizard-step__heading{margin-bottom:1.5rem}.e-app-import-export-wizard-step__description,.e-app-import-export-wizard-step__info{color:var(--e-app-import-export-wizard-step-text-color)}.e-app-import-export-wizard-step__info{margin-top:1.5rem}.e-app-import-export-wizard-step__content{text-align:initial;margin-bottom:1.25rem}.e-app-import-export-wizard-step__notice{display:block;position:sticky;top:100%;color:var(--e-app-import-export-wizard-step-bottom-text-color);font-style:italic;margin-bottom:0}.eps-theme-dark .e-app-import-export-wizard-step{--e-app-import-export-wizard-step-icon-color:#64666a;--e-app-import-export-wizard-step-text-color:#b4b5b7;--e-app-import-export-wizard-step-bottom-text-color:#b4b5b7}.e-app-import-export-page-header{--e-app-import-export-page-header-border-bottom-color:#d5dadf;--e-app-import-export-page-header-heading-color:#6d7882;--e-app-import-export-page-header-description-color:#a4afb7;border-bottom:1px solid var(--e-app-import-export-page-header-border-bottom-color);margin-bottom:2.75rem}.e-app-import-export-page-header__content-wrapper{max-width:645px}.e-app-import-export-page-header__heading{color:var(--e-app-import-export-page-header-heading-color)}.e-app-import-export-page-header__description{color:var(--e-app-import-export-page-header-description-color);margin-top:1.25rem}.eps-theme-dark .e-app-import-export-page-header{--e-app-import-export-page-header-border-bottom-color:#4c4f56;--e-app-import-export-page-header-heading-color:#e0e1e3;--e-app-import-export-page-header-description-color:#e0e1e3}.e-app-wizard-footer{--e-app-wizard-footer-border-color:#d5dadf;padding:.5rem}.e-app-wizard-footer__separator{border-top:1px solid var(--e-app-wizard-footer-border-color)}.eps-theme-dark .e-app-wizard-footer{--e-app-wizard-footer-border-color:#4c4f56}.e-app-export-templates-features__locked{--e-app-export-templates-features-locked-color:#a4afb7;color:var(--e-app-export-templates-features-locked-color)}.eps-theme-dark .e-app-export-templates-features__locked{--e-app-export-templates-features-locked-color:#7d7e82}:root{--color-box-shadow-color:rgba(0,0,0,0.05)}.eps-theme-dark{--color-box-shadow-color:rgba(0,0,0,0.1)}:root{--card-background-color:hsla(0,0%,100%,0.5);--card-background-color-hover:#fff;--card-box-shadow:0 4px 10px var(--color-box-shadow-color);--card-header-footer-border:1px solid #f1f3f5;--card-header-footer-active-border:2px solid #e2e6ea;--card-headline-color:#6d7882;--card-figure-background-color:#f1f3f5;--card-image-overlay-background-color:rgba(0,0,0,0.2)}.eps-theme-dark{--card-background-color:#404349;--card-background-color-hover:rgba(76,79,86,0.8);--card-box-shadow:0 3px 6px var(--color-box-shadow-color);--card-header-footer-border:1px solid #34383c;--card-header-footer-active-border:1px solid #26292c;--card-headline-color:#e0e1e3;--card-figure-background-color:#34383c;--card-image-overlay-background-color:rgba(52,56,60,0.5)}.eps-card{background-color:var(--card-background-color);-webkit-box-shadow:0 4px 10px var(--color-box-shadow-color);box-shadow:0 4px 10px var(--color-box-shadow-color);border-radius:.1875rem;-webkit-transition:.3s;-o-transition:.3s;transition:.3s;font-size:.75rem}.eps-card__header{padding:.625rem;border-bottom:var(--card-header-footer-border);min-height:2.5rem;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.eps-card__header--padding{padding:var(--eps-card-header-padding)}.eps-card__headline{color:var(--card-headline-color);margin-bottom:0;font-weight:500;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;overflow:hidden;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap}.eps-card__body{padding:.625rem}.eps-card__body--padding{padding:var(--eps-card-body-padding)}.eps-card__figure{background-color:var(--card-figure-background-color);position:relative;padding-bottom:var(--card-image-aspect-ratio,95.6%);overflow:hidden;height:0}.eps-card__image{width:100%;-o-object-fit:contain;object-fit:contain;-o-object-position:top;object-position:top;position:absolute;top:0;left:0}.eps-card__image-overlay{position:absolute;top:0;background-color:var(--card-image-overlay-background-color);z-index:1;width:100%;height:100%;opacity:0;-webkit-transition:.3s;-o-transition:.3s;transition:.3s}.eps-card__image-overlay:hover{opacity:1}.eps-card__footer{padding:.625rem;border-top:var(--card-header-footer-border);font-size:.6875rem}.eps-card__footer--padding{padding:var(--eps-card-footer-padding)}.eps-card:hover{background-color:var(--card-background-color-hover)}:root{--menu-item-color:#6d7882;--menu-item-color-hover:#556068;--menu-item-background-color-active:#fff;--menu-item-icon-color:#a4afb7;--menu-item-action-button-color:#d5dadf}.eps-theme-dark{--menu-item-color:#e0e1e3;--menu-item-color-hover:#e0e1e3;--menu-item-background-color-active:#404349;--menu-item-icon-color:#b4b5b7;--menu-item-action-button-color:#64666a}.eps-menu-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;position:relative;-webkit-transition:.3s;-o-transition:.3s;transition:.3s;--action-button-opacity:0}.eps-menu-item:before{content:"";display:block;position:absolute;top:0;inset-inline-start:0;height:100%;width:var(--menu-item-pointer-width);background-color:#58d0f5}.eps-menu-item:hover{--action-button-opacity:1}.eps-menu-item--active,.eps-menu-item:hover{--menu-item-color:var(--menu-item-color-hover);--eps-link-color:var(--menu-item-color-hover);--menu-item-icon-color:#58d0f5}.eps-menu-item--active{background-color:var(--menu-item-background-color-active)}.eps-menu-item__link{padding:.5rem 1.875rem;min-height:2.75rem;font-size:.75rem;line-height:1.2;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:var(--menu-item-color);--eps-link-color:var(--menu-item-color)}.eps-menu-item__link:not(:last-child){-webkit-padding-end:0;padding-inline-end:0}.eps-menu-item__link .eps-icon{font-size:1.125rem;color:var(--menu-item-icon-color);-webkit-margin-end:.75rem;margin-inline-end:.75rem}.eps-menu-item__action-button{color:var(--menu-item-action-button-color);opacity:var(--action-button-opacity);padding:.625rem;-webkit-transition:.3s;-o-transition:.3s;transition:.3s;-webkit-margin-end:1.25rem;margin-inline-end:1.25rem}.eps-menu-item--active{--menu-item-pointer-width:0.3125rem;-webkit-box-shadow:0 3px 6px var(--color-box-shadow-color);box-shadow:0 3px 6px var(--color-box-shadow-color)}.eps-menu-item--active .eps-menu-item__link .eps-icon{color:#58d0f5}.eps-grid-container{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;width:100%}.eps-grid-container--no-wrap{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.eps-grid-container--wrap-reverse{-ms-flex-wrap:wrap-reverse;flex-wrap:wrap-reverse}.eps-grid-container--spacing{--grid-row-gutter:calc(-1 * calc(var(--grid-spacing-gutter) * (0.625rem / 10)));width:var(--grid-spacing-width);margin:var(--grid-row-gutter)}.eps-grid-container--spacing>.eps-grid-item{padding:var(--grid-spacing-gutter)}.eps-grid--direction-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.eps-grid--direction-row-reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.eps-grid--direction-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.eps-grid--direction-column-reverse{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.eps-grid--justify-stretch{-webkit-box-pack:stretch;-ms-flex-pack:stretch;justify-content:stretch}.eps-grid--justify-start{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.eps-grid--justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.eps-grid--justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.eps-grid--justify-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.eps-grid--justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.eps-grid--justify-space-evenly{-webkit-box-pack:space-evenly;-ms-flex-pack:space-evenly;justify-content:space-evenly}.eps-grid--align-content-stretch{-ms-flex-line-pack:stretch;align-content:stretch}.eps-grid--align-content-start{-ms-flex-line-pack:start;align-content:flex-start}.eps-grid--align-content-center{-ms-flex-line-pack:center;align-content:center}.eps-grid--align-content-end{-ms-flex-line-pack:end;align-content:flex-end}.eps-grid--align-content-space-between{-ms-flex-line-pack:justify;align-content:space-between}.eps-grid--align-items-start{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.eps-grid--align-items-center{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.eps-grid--align-items-end{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.eps-grid--align-items-baseline{-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.eps-grid--align-items-stretch{-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}.eps-grid-item--zero-min-width{min-width:0}@media screen and (min-width:480px){.eps-grid-item-sm{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%;-ms-flex-preferred-size:0;flex-basis:0}}@media screen and (min-width:768px){.eps-grid-item-md{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%;-ms-flex-preferred-size:0;flex-basis:0}}@media screen and (min-width:1025px){.eps-grid-item-lg{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%;-ms-flex-preferred-size:0;flex-basis:0}}@media screen and (min-width:1440px){.eps-grid-item-xl{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%;-ms-flex-preferred-size:0;flex-basis:0}}@media screen and (min-width:1600px){.eps-grid-item-xxl{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%;-ms-flex-preferred-size:0;flex-basis:0}}.eps-grid-item-xs-1{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(1 / 12 * 100%);-ms-flex-preferred-size:calc(1 / 12 * 100%);flex-basis:calc(1 / 12 * 100%)}.eps-grid-item-xs-2{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(2 / 12 * 100%);-ms-flex-preferred-size:calc(2 / 12 * 100%);flex-basis:calc(2 / 12 * 100%)}.eps-grid-item-xs-3{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(3 / 12 * 100%);-ms-flex-preferred-size:calc(3 / 12 * 100%);flex-basis:calc(3 / 12 * 100%)}.eps-grid-item-xs-4{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(4 / 12 * 100%);-ms-flex-preferred-size:calc(4 / 12 * 100%);flex-basis:calc(4 / 12 * 100%)}.eps-grid-item-xs-5{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(5 / 12 * 100%);-ms-flex-preferred-size:calc(5 / 12 * 100%);flex-basis:calc(5 / 12 * 100%)}.eps-grid-item-xs-6{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(6 / 12 * 100%);-ms-flex-preferred-size:calc(6 / 12 * 100%);flex-basis:calc(6 / 12 * 100%)}.eps-grid-item-xs-7{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(7 / 12 * 100%);-ms-flex-preferred-size:calc(7 / 12 * 100%);flex-basis:calc(7 / 12 * 100%)}.eps-grid-item-xs-8{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(8 / 12 * 100%);-ms-flex-preferred-size:calc(8 / 12 * 100%);flex-basis:calc(8 / 12 * 100%)}.eps-grid-item-xs-9{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(9 / 12 * 100%);-ms-flex-preferred-size:calc(9 / 12 * 100%);flex-basis:calc(9 / 12 * 100%)}.eps-grid-item-xs-10{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(10 / 12 * 100%);-ms-flex-preferred-size:calc(10 / 12 * 100%);flex-basis:calc(10 / 12 * 100%)}.eps-grid-item-xs-11{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(11 / 12 * 100%);-ms-flex-preferred-size:calc(11 / 12 * 100%);flex-basis:calc(11 / 12 * 100%)}.eps-grid-item-xs-12{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(12 / 12 * 100%);-ms-flex-preferred-size:calc(12 / 12 * 100%);flex-basis:calc(12 / 12 * 100%)}@media screen and (min-width:480px){.eps-grid-item-sm-1{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(1 / 12 * 100%);-ms-flex-preferred-size:calc(1 / 12 * 100%);flex-basis:calc(1 / 12 * 100%)}.eps-grid-item-sm-2{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(2 / 12 * 100%);-ms-flex-preferred-size:calc(2 / 12 * 100%);flex-basis:calc(2 / 12 * 100%)}.eps-grid-item-sm-3{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(3 / 12 * 100%);-ms-flex-preferred-size:calc(3 / 12 * 100%);flex-basis:calc(3 / 12 * 100%)}.eps-grid-item-sm-4{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(4 / 12 * 100%);-ms-flex-preferred-size:calc(4 / 12 * 100%);flex-basis:calc(4 / 12 * 100%)}.eps-grid-item-sm-5{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(5 / 12 * 100%);-ms-flex-preferred-size:calc(5 / 12 * 100%);flex-basis:calc(5 / 12 * 100%)}.eps-grid-item-sm-6{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(6 / 12 * 100%);-ms-flex-preferred-size:calc(6 / 12 * 100%);flex-basis:calc(6 / 12 * 100%)}.eps-grid-item-sm-7{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(7 / 12 * 100%);-ms-flex-preferred-size:calc(7 / 12 * 100%);flex-basis:calc(7 / 12 * 100%)}.eps-grid-item-sm-8{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(8 / 12 * 100%);-ms-flex-preferred-size:calc(8 / 12 * 100%);flex-basis:calc(8 / 12 * 100%)}.eps-grid-item-sm-9{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(9 / 12 * 100%);-ms-flex-preferred-size:calc(9 / 12 * 100%);flex-basis:calc(9 / 12 * 100%)}.eps-grid-item-sm-10{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(10 / 12 * 100%);-ms-flex-preferred-size:calc(10 / 12 * 100%);flex-basis:calc(10 / 12 * 100%)}.eps-grid-item-sm-11{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(11 / 12 * 100%);-ms-flex-preferred-size:calc(11 / 12 * 100%);flex-basis:calc(11 / 12 * 100%)}.eps-grid-item-sm-12{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(12 / 12 * 100%);-ms-flex-preferred-size:calc(12 / 12 * 100%);flex-basis:calc(12 / 12 * 100%)}}@media screen and (min-width:768px){.eps-grid-item-md-1{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(1 / 12 * 100%);-ms-flex-preferred-size:calc(1 / 12 * 100%);flex-basis:calc(1 / 12 * 100%)}.eps-grid-item-md-2{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(2 / 12 * 100%);-ms-flex-preferred-size:calc(2 / 12 * 100%);flex-basis:calc(2 / 12 * 100%)}.eps-grid-item-md-3{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(3 / 12 * 100%);-ms-flex-preferred-size:calc(3 / 12 * 100%);flex-basis:calc(3 / 12 * 100%)}.eps-grid-item-md-4{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(4 / 12 * 100%);-ms-flex-preferred-size:calc(4 / 12 * 100%);flex-basis:calc(4 / 12 * 100%)}.eps-grid-item-md-5{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(5 / 12 * 100%);-ms-flex-preferred-size:calc(5 / 12 * 100%);flex-basis:calc(5 / 12 * 100%)}.eps-grid-item-md-6{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(6 / 12 * 100%);-ms-flex-preferred-size:calc(6 / 12 * 100%);flex-basis:calc(6 / 12 * 100%)}.eps-grid-item-md-7{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(7 / 12 * 100%);-ms-flex-preferred-size:calc(7 / 12 * 100%);flex-basis:calc(7 / 12 * 100%)}.eps-grid-item-md-8{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(8 / 12 * 100%);-ms-flex-preferred-size:calc(8 / 12 * 100%);flex-basis:calc(8 / 12 * 100%)}.eps-grid-item-md-9{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(9 / 12 * 100%);-ms-flex-preferred-size:calc(9 / 12 * 100%);flex-basis:calc(9 / 12 * 100%)}.eps-grid-item-md-10{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(10 / 12 * 100%);-ms-flex-preferred-size:calc(10 / 12 * 100%);flex-basis:calc(10 / 12 * 100%)}.eps-grid-item-md-11{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(11 / 12 * 100%);-ms-flex-preferred-size:calc(11 / 12 * 100%);flex-basis:calc(11 / 12 * 100%)}.eps-grid-item-md-12{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(12 / 12 * 100%);-ms-flex-preferred-size:calc(12 / 12 * 100%);flex-basis:calc(12 / 12 * 100%)}}@media screen and (min-width:1025px){.eps-grid-item-lg-1{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(1 / 12 * 100%);-ms-flex-preferred-size:calc(1 / 12 * 100%);flex-basis:calc(1 / 12 * 100%)}.eps-grid-item-lg-2{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(2 / 12 * 100%);-ms-flex-preferred-size:calc(2 / 12 * 100%);flex-basis:calc(2 / 12 * 100%)}.eps-grid-item-lg-3{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(3 / 12 * 100%);-ms-flex-preferred-size:calc(3 / 12 * 100%);flex-basis:calc(3 / 12 * 100%)}.eps-grid-item-lg-4{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(4 / 12 * 100%);-ms-flex-preferred-size:calc(4 / 12 * 100%);flex-basis:calc(4 / 12 * 100%)}.eps-grid-item-lg-5{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(5 / 12 * 100%);-ms-flex-preferred-size:calc(5 / 12 * 100%);flex-basis:calc(5 / 12 * 100%)}.eps-grid-item-lg-6{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(6 / 12 * 100%);-ms-flex-preferred-size:calc(6 / 12 * 100%);flex-basis:calc(6 / 12 * 100%)}.eps-grid-item-lg-7{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(7 / 12 * 100%);-ms-flex-preferred-size:calc(7 / 12 * 100%);flex-basis:calc(7 / 12 * 100%)}.eps-grid-item-lg-8{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(8 / 12 * 100%);-ms-flex-preferred-size:calc(8 / 12 * 100%);flex-basis:calc(8 / 12 * 100%)}.eps-grid-item-lg-9{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(9 / 12 * 100%);-ms-flex-preferred-size:calc(9 / 12 * 100%);flex-basis:calc(9 / 12 * 100%)}.eps-grid-item-lg-10{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(10 / 12 * 100%);-ms-flex-preferred-size:calc(10 / 12 * 100%);flex-basis:calc(10 / 12 * 100%)}.eps-grid-item-lg-11{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(11 / 12 * 100%);-ms-flex-preferred-size:calc(11 / 12 * 100%);flex-basis:calc(11 / 12 * 100%)}.eps-grid-item-lg-12{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(12 / 12 * 100%);-ms-flex-preferred-size:calc(12 / 12 * 100%);flex-basis:calc(12 / 12 * 100%)}}@media screen and (min-width:1440px){.eps-grid-item-xl-1{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(1 / 12 * 100%);-ms-flex-preferred-size:calc(1 / 12 * 100%);flex-basis:calc(1 / 12 * 100%)}.eps-grid-item-xl-2{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(2 / 12 * 100%);-ms-flex-preferred-size:calc(2 / 12 * 100%);flex-basis:calc(2 / 12 * 100%)}.eps-grid-item-xl-3{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(3 / 12 * 100%);-ms-flex-preferred-size:calc(3 / 12 * 100%);flex-basis:calc(3 / 12 * 100%)}.eps-grid-item-xl-4{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(4 / 12 * 100%);-ms-flex-preferred-size:calc(4 / 12 * 100%);flex-basis:calc(4 / 12 * 100%)}.eps-grid-item-xl-5{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(5 / 12 * 100%);-ms-flex-preferred-size:calc(5 / 12 * 100%);flex-basis:calc(5 / 12 * 100%)}.eps-grid-item-xl-6{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(6 / 12 * 100%);-ms-flex-preferred-size:calc(6 / 12 * 100%);flex-basis:calc(6 / 12 * 100%)}.eps-grid-item-xl-7{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(7 / 12 * 100%);-ms-flex-preferred-size:calc(7 / 12 * 100%);flex-basis:calc(7 / 12 * 100%)}.eps-grid-item-xl-8{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(8 / 12 * 100%);-ms-flex-preferred-size:calc(8 / 12 * 100%);flex-basis:calc(8 / 12 * 100%)}.eps-grid-item-xl-9{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(9 / 12 * 100%);-ms-flex-preferred-size:calc(9 / 12 * 100%);flex-basis:calc(9 / 12 * 100%)}.eps-grid-item-xl-10{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(10 / 12 * 100%);-ms-flex-preferred-size:calc(10 / 12 * 100%);flex-basis:calc(10 / 12 * 100%)}.eps-grid-item-xl-11{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(11 / 12 * 100%);-ms-flex-preferred-size:calc(11 / 12 * 100%);flex-basis:calc(11 / 12 * 100%)}.eps-grid-item-xl-12{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(12 / 12 * 100%);-ms-flex-preferred-size:calc(12 / 12 * 100%);flex-basis:calc(12 / 12 * 100%)}}@media screen and (min-width:1600px){.eps-grid-item-xxl-1{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(1 / 12 * 100%);-ms-flex-preferred-size:calc(1 / 12 * 100%);flex-basis:calc(1 / 12 * 100%)}.eps-grid-item-xxl-2{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(2 / 12 * 100%);-ms-flex-preferred-size:calc(2 / 12 * 100%);flex-basis:calc(2 / 12 * 100%)}.eps-grid-item-xxl-3{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(3 / 12 * 100%);-ms-flex-preferred-size:calc(3 / 12 * 100%);flex-basis:calc(3 / 12 * 100%)}.eps-grid-item-xxl-4{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(4 / 12 * 100%);-ms-flex-preferred-size:calc(4 / 12 * 100%);flex-basis:calc(4 / 12 * 100%)}.eps-grid-item-xxl-5{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(5 / 12 * 100%);-ms-flex-preferred-size:calc(5 / 12 * 100%);flex-basis:calc(5 / 12 * 100%)}.eps-grid-item-xxl-6{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(6 / 12 * 100%);-ms-flex-preferred-size:calc(6 / 12 * 100%);flex-basis:calc(6 / 12 * 100%)}.eps-grid-item-xxl-7{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(7 / 12 * 100%);-ms-flex-preferred-size:calc(7 / 12 * 100%);flex-basis:calc(7 / 12 * 100%)}.eps-grid-item-xxl-8{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(8 / 12 * 100%);-ms-flex-preferred-size:calc(8 / 12 * 100%);flex-basis:calc(8 / 12 * 100%)}.eps-grid-item-xxl-9{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(9 / 12 * 100%);-ms-flex-preferred-size:calc(9 / 12 * 100%);flex-basis:calc(9 / 12 * 100%)}.eps-grid-item-xxl-10{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(10 / 12 * 100%);-ms-flex-preferred-size:calc(10 / 12 * 100%);flex-basis:calc(10 / 12 * 100%)}.eps-grid-item-xxl-11{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(11 / 12 * 100%);-ms-flex-preferred-size:calc(11 / 12 * 100%);flex-basis:calc(11 / 12 * 100%)}.eps-grid-item-xxl-12{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(12 / 12 * 100%);-ms-flex-preferred-size:calc(12 / 12 * 100%);flex-basis:calc(12 / 12 * 100%)}}.eps-theme-dark,:root{--menu-title-color:#6d7882}.eps-menu ul{list-style:none;padding:0;margin:0}.eps-menu ul li{display:-webkit-box;display:-ms-flexbox;display:flex}.eps-menu__title{padding:.5rem 1.875rem;font-size:.6875rem;line-height:1.2;text-transform:uppercase;font-weight:400;color:var(--menu-title-color)}:root{--eps-modal-background-color:#fff;--eps-modal-header-background-color:#58d0f5}.eps-theme-dark{--eps-modal-background-color:#34383c;--eps-modal-header-background-color:#58d0f5}.eps-modal{max-width:43.75rem;background:var(--eps-modal-background-color);border-radius:.1875rem;-webkit-animation:eps-animation-pop .15s cubic-bezier(.57,.53,.71,1.47) forwards;animation:eps-animation-pop .15s cubic-bezier(.57,.53,.71,1.47) forwards}.eps-modal__overlay{background:rgba(0,0,0,.5);position:fixed;display:-webkit-box;display:-ms-flexbox;display:flex;top:0;left:0;width:100%;height:100%;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;z-index:1030}.eps-modal__header{font-size:.875rem;background:var(--eps-modal-header-background-color);height:2.75rem;padding:.625rem 1rem;border-radius:.1875rem}.eps-modal__header,.eps-modal__header .title{color:#fff}.eps-modal__icon{-webkit-margin-end:.625rem;margin-inline-end:.625rem}.eps-modal__body{padding:1.875rem}.eps-modal .eps-tip,.eps-modal__tip{-webkit-padding-start:.75rem;padding-inline-start:.75rem;-webkit-border-start:3px solid #58d0f5;border-inline-start:3px solid #58d0f5}.eps-modal .eps-tip:not(:last-child),.eps-modal__tip:not(:last-child){margin-bottom:1.875rem}.eps-modal .eps-tip:not(:first-child),.eps-modal__section:not(:first-child),.eps-modal__tip:not(:first-child){margin-top:1.875rem}.eps-modal__close-wrapper{-webkit-padding-start:1rem;padding-inline-start:1rem;-webkit-border-start:solid 1px #fff;border-inline-start:solid 1px #fff}.eps-add-new-button{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;--eps-add-new-button-size:1.5rem;line-height:var(--eps-add-new-button-size);cursor:pointer}.eps-add-new-button .eps-icon{background-color:#58d0f5;color:#fff;width:var(--eps-add-new-button-size);height:var(--eps-add-new-button-size);border-radius:100%;font-size:calc(var(--eps-add-new-button-size) * .75);text-align:center;line-height:var(--eps-add-new-button-size)}.eps-add-new-button span:not(.sr-only){-webkit-margin-start:.625rem;margin-inline-start:.625rem;font-weight:500}.eps-add-new-button--sm{--eps-add-new-button-size:1rem}:root{--select2-selection-background-color:#fff;--select2-selection-color:#6d7882;--select2-selection-border-color:#d5dadf;--select2-selection-opened-focused-border-color:#f1f3f5;--select2-single-selection-rendered-color:#6d7882;--select2-default-single-selection-background-color:#fff;--select2-default-single-selection-border-color:#d5dadf;--select2-default-multiple-selection-background-color:#fff;--select2-default-multiple-selection-choice-background-color:#f1f3f5;--select2-default-multiple-selection-choice-color:#6d7882;--select2-default-multiple-selection-choice-border-color:#f1f3f5;--select2-default-multiple-selection-choice-remove-color:#a4afb7;--select2-default-multiple-selection-choice-remove-hover-color:#6d7882;--select2-default-results-selected-option-background-color:#fff;--select2-default-results-selected-option-color:#6d7882;--select2-default-results-highlighted-option-background-color:#5897fb;--select2-default-results-highlighted-option-color:#fff;--select2-results-selected-option-background-color:#5897fb;--select2-results-selected-option-color:#fff;--select2-dropdown-background-color:#fff;--select2-dropdown-border-color:#d5dadf}.eps-theme-dark{--select2-selection-background-color:#34383c;--select2-selection-color:#e0e1e3;--select2-selection-border-color:#64666a;--select2-selection-opened-focused-border-color:#7d7e82;--select2-single-selection-rendered-color:#e0e1e3;--select2-default-single-selection-background-color:#34383c;--select2-default-single-selection-border-color:#4c4f56;--select2-default-multiple-selection-background-color:#34383c;--select2-default-multiple-selection-choice-background-color:#4c4f56;--select2-default-multiple-selection-choice-color:#e0e1e3;--select2-default-multiple-selection-choice-border-color:#4c4f56;--select2-default-multiple-selection-choice-remove-color:#b4b5b7;--select2-default-multiple-selection-choice-remove-hover-color:#e0e1e3;--select2-default-results-selected-option-background-color:#34383c;--select2-default-results-selected-option-color:#e0e1e3;--select2-default-results-highlighted-option-background-color:#4c4f56;--select2-default-results-highlighted-option-color:#e0e1e3;--select2-results-selected-option-background-color:#4c4f56;--select2-results-selected-option-color:#e0e1e3;--select2-dropdown-background-color:#34383c;--select2-dropdown-border-color:#64666a}.select2-container:not(.select2-container--open):not(.select2-container--focus) .select2-selection--multiple,.select2-container:not(.select2-container--open):not(.select2-container--focus) .select2-selection--single{background-color:var(--select2-selection-background-color);color:var(--select2-selection-color);border-color:var(--select2-selection-border-color)}.select2-container.select2-container--focus .select2-selection--multiple,.select2-container.select2-container--focus .select2-selection--single,.select2-container.select2-container--open .select2-selection--multiple,.select2-container.select2-container--open .select2-selection--single{border-color:var(--select2-selection-opened-focused-border-color)}.select2-container.select2-container--default .select2-selection--single .select2-selection__rendered{color:var(--select2-single-selection-rendered-color)}.select2-container--default .select2-selection--single{background-color:var(--select2-default-single-selection-background-color);border-color:var(--select2-default-single-selection-border-color)}.select2-container--default .select2-selection--multiple{background-color:var(--select2-default-multiple-selection-background-color)}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:var(--select2-default-multiple-selection-choice-background-color);color:var(--select2-default-multiple-selection-choice-color);border-color:var(--select2-default-multiple-selection-choice-border-color)}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:var(--select2-default-multiple-selection-choice-remove-color)}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:var(--select2-default-multiple-selection-choice-remove-hover-color)}.select2-container--default .select2-results__option[aria-selected]{background-color:var(--select2-default-results-selected-option-background-color);color:var(--select2-default-results-selected-option-color)}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:var(--select2-default-results-highlighted-option-background-color);color:var(--select2-default-results-highlighted-option-color)}.select2-container .select2-results__option[aria-selected=true]{background-color:var(--select2-results-selected-option-background-color);color:var(--select2-results-selected-option-color)}.select2-container .select2-dropdown{background-color:var(--select2-dropdown-background-color);border-color:var(--select2-dropdown-border-color)}.eps-notice{--eps-box-notice-color:#a4afb7;--eps-box-notice-background-color:#fcfcfc;padding:.625rem 1rem;-webkit-box-shadow:0 2px 3px 1px var(--color-box-shadow-color);box-shadow:0 2px 3px 1px var(--color-box-shadow-color);background-color:var(--eps-box-notice-background-color)}.eps-notice-semantic{-webkit-border-start:3px solid var(--eps-notice-semantic-color);border-inline-start:3px solid var(--eps-notice-semantic-color)}.eps-notice-semantic .eps-notice__icon{color:var(--eps-notice-semantic-color);font-size:1rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem}.eps-notice--warning{--eps-notice-semantic-color:#fcb92c}.eps-notice--danger{--eps-notice-semantic-color:#b01b1b}.eps-notice--info{--eps-notice-semantic-color:#58d0f5}.eps-notice__text{margin:0;padding:0;color:var(--eps-box-notice-color);font-style:italic}.eps-notice__button-container{-ms-flex-negative:0;flex-shrink:0;margin-left:1.25rem;width:auto}.eps-theme-dark .eps-notice{--eps-box-notice-color:#b4b5b7;--eps-box-notice-background-color:#404349}.eps-list{--eps-list-item-separated-border-color:#f1f3f5;padding:0;margin:0;list-style-type:none}.eps-list--padding{padding:var(--eps-list-padding)}.eps-list__item{padding:0}.eps-list__item--padding{padding:var(--eps-list-item-padding)}.eps-list--separated .eps-list__item:not(:last-child){border-bottom:1px solid var(--eps-list-item-separated-border-color)}.eps-theme-dark .eps-list{--eps-list-item-separated-border-color:#34383c}:root{--popover-background-color:#fff;--popover-item-color:#6d7882;--popover-item-hover-color:#556068;--popover-item-danger-hover-color:#b01b1b;--popover-item-background-color:#fff;--popover-box-shadow-color:rgba(0,0,0,0.15);--popover-box-shadow-size:0px 1px 20px;--popover-arrow-color:#fff}.eps-theme-dark{--popover-background-color:#4c4f56;--popover-item-color:#fff;--popover-item-hover-color:#e0e1e3;--popover-item-danger-hover-color:#f84343;--popover-item-background-color:#4c4f56;--popover-box-shadow-color:rgba(0,0,0,0.15);--popover-box-shadow-size:0px 1px 20px;--popover-arrow-color:#4c4f56}.eps-popover{padding:10px 0;background-color:var(--popover-background-color);-webkit-box-shadow:var(--popover-box-shadow-size) var(--popover-box-shadow-color);box-shadow:var(--popover-box-shadow-size) var(--popover-box-shadow-color);list-style:none;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;min-width:120px;border-radius:.1875rem;position:absolute;z-index:1050;margin-top:9px;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);left:.25rem}.eps-popover__background{position:fixed;top:0;bottom:0;left:0;right:0;z-index:1030}.eps-popover__container{position:relative}.eps-popover:before{content:"";display:block;position:absolute;width:16px;height:9px;margin:0 .1875rem 9px;top:-9px;left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);border-left:calc(16px / 2) solid transparent;border-right:calc(16px / 2) solid transparent;border-top:0 solid transparent;border-bottom:9px solid transparent;border-bottom-color:var(--popover-arrow-color)}.eps-popover__item{padding:.3125rem 1rem;background-color:var(--popover-item-background-color);color:var(--popover-item-color);font-size:.6875rem;font-weight:500;line-height:.8125rem;width:100%;-webkit-box-align:center;-ms-flex-align:center;align-items:center;cursor:pointer}.eps-popover__item:hover{color:var(--popover-item-hover-color)}.eps-popover__item--danger:hover{color:var(--popover-item-danger-hover-color)}.eps-popover__item .eps-icon{font-size:inherit;-webkit-margin-end:.3125rem;margin-inline-end:.3125rem}.eps-css-grid{display:grid;grid-template-columns:repeat(var(--eps-grid-columns,auto-fill),minmax(var(--eps-grid-col-min-width,1fr),var(--eps-grid-col-max-width,1fr)));grid-gap:var(--eps-grid-spacing)}.eps-box{--eps-box-background-color:#fff;--eps-box-color:#495157;--eps-box-input-color:#495157;padding:0;border-radius:.1875rem;background-color:var(--eps-box-background-color);color:var(--eps-box-color)}.eps-box--padding{padding:var(--eps-box-padding)}.eps-box>input{width:100%;outline:0;border:0;background-color:var(--eps-box-background-color);color:var(--eps-box-input-color)}.eps-theme-dark .eps-box{--eps-box-background-color:#404349;--eps-box-color:#e0e1e3;--eps-box-input-color:#e0e1e3}:root{--checkbox-border-color:#d5dadf;--checkbox-hover-border-color:#c7cdd4;--checkbox-active-border-color:#e3e7ea;--checkbox-background-color:#fff;--checkbox-checked-background-color:#39b54a;--checkbox-checked-hover-background-color:#33a242;--checkbox-checked-active-background-color:#44c455;--checkbox-checked-disabled-background-color:#c2cbd2;--checkbox-indicator-color:#fff;--checkbox-error-background-color:#b01b1b}.eps-theme-dark{--checkbox-background-color:transparent}.eps-checkbox{-webkit-appearance:none;border-radius:.1875rem;width:15px;height:15px;outline:0;background-color:var(--checkbox-background-color);display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid var(--checkbox-border-color)}.eps-checkbox:after{display:inline-block;margin-bottom:calc(.25 / 2 * 100%);content:" ";width:3px;height:6px;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.eps-checkbox:hover{--checkbox-border-color:var(--checkbox-hover-border-color)}.eps-checkbox:active{--checkbox-border-color:var(--checkbox-active-border-color)}.eps-checkbox:checked{--checkbox-background-color:var(--checkbox-checked-background-color);--checkbox-border-color:var(--checkbox-checked-background-color)}.eps-checkbox:checked:after{border:solid #fff;border-width:0 1px 1px 0}.eps-checkbox:checked:hover{--checkbox-background-color:var(--checkbox-checked-hover-background-color);--checkbox-border-color:var(--checkbox-checked-hover-background-color)}.eps-checkbox:checked:active{--checkbox-background-color:var(--checkbox-checked-active-background-color);--checkbox-border-color:var(--checkbox-checked-active-background-color)}.eps-checkbox:checked:disabled{--checkbox-background-color:var(--checkbox-checked-disabled-background-color);--checkbox-border-color:var(--checkbox-checked-disabled-background-color)}.eps-checkbox--rounded{border-radius:50%}.eps-checkbox--indeterminate{--checkbox-background-color:var(--checkbox-checked-background-color);--checkbox-border-color:var(--checkbox-checked-background-color)}.eps-checkbox--indeterminate:after{display:inline-block;margin-bottom:0;content:" ";width:7px;height:0;-webkit-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg);border-top:1px solid #fff}.eps-checkbox--error:after,.eps-checkbox--error:before,.eps-checkbox--error:checked:after,.eps-checkbox--error:checked:before{display:inline-block;margin-bottom:0;content:" ";width:7px;height:0;border:solid #fff;border-width:1px 0 0;position:absolute}.eps-checkbox--error:before,.eps-checkbox--error:checked:before{-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.eps-checkbox--error:after,.eps-checkbox--error:checked:after{-webkit-transform:rotate(-45deg);-ms-transform:rotate(-45deg);transform:rotate(-45deg)}.eps-checkbox--error,.eps-checkbox--error:checked,.eps-checkbox--error:checked:hover,.eps-checkbox--error:hover{--checkbox-background-color:var(--checkbox-error-background-color);--checkbox-border-color:var(--checkbox-error-background-color)}:root{--e-app-drag-drop-background-color:#fff;--e-app-drag-drop-outline-color:#d5dadf}.eps-theme-dark{--e-app-drag-drop-background-color:#404349;--e-app-drag-drop-outline-color:#7d7e82}.e-app-drag-drop{background-color:var(--e-app-drag-drop-background-color);outline:2px dashed var(--e-app-drag-drop-outline-color);outline-offset:-.75rem;margin-bottom:1.5rem;padding:4.125rem;text-align:center}.e-app-drag-drop--drag-over{outline-color:#58d0f5}.eps-dialog{border-radius:3px;width:375px}.eps-dialog__close-button{position:absolute;top:-2.75rem;right:-2.75rem;margin-top:.625rem;margin-right:.625rem;z-index:1040;font-size:1.25rem;color:#fff}.eps-dialog__content{padding:1.5rem 1.875rem 1rem;font-size:.75rem}.eps-dialog__text,.eps-dialog__title{text-align:center}.eps-dialog__buttons{display:-webkit-box;display:-ms-flexbox;display:flex}.eps-dialog__button{-webkit-box-flex:1;-ms-flex:1;flex:1;border-top:1px solid var(--hr-color);line-height:2.75rem;text-align:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.eps-dialog__button:last-child:not(:first-child){-webkit-border-start:1px solid var(--hr-color);border-inline-start:1px solid var(--hr-color)}.e-app__popover{display:none;position:absolute;-webkit-box-shadow:0 2px 15px rgba(0,0,0,.3);box-shadow:0 2px 15px rgba(0,0,0,.3);border-radius:6px;padding:20px;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;z-index:999;background-color:#fff}.e-app__popover:before{content:"";position:absolute;top:-16px;left:var(--popover-arrow-offset-end,22px);border:8px solid transparent;border-bottom-color:#fff}.eps-inline-link{color:var(--eps-inline-link-color);background-color:initial;border:0;padding:0}.eps-inline-link--color-primary{--eps-inline-link-color:#39b54a}.eps-inline-link--color-secondary{--eps-inline-link-color:#c2cbd2}.eps-inline-link--color-danger{--eps-inline-link-color:#b01b1b}.eps-inline-link--color-cta{--eps-inline-link-color:#93003f}.eps-inline-link--color-link{--eps-inline-link-color:#58d0f5}.eps-inline-link--color-disabled{--eps-inline-link-color:#c2cbd2}.eps-inline-link--underline-always,.eps-inline-link--underline-always:hover,.eps-inline-link--underline-hover:hover{text-decoration:underline}.eps-inline-link--italic{font-style:italic}.eps-inline-link,.eps-inline-link:focus{outline:none}.eps-text-field{--eps-text-field-color:#6d7882;--eps-text-field-background-color:#fff;--eps-text-field-placeholder-color:#a4afb7;--eps-text-field-outlined-border-color:#d5dadf;--eps-text-field-outlined-focus-border-color:#c2cbd2;width:100%;color:var(--eps-text-field-color);background-color:var(--eps-text-field-background-color);border:0;outline:none;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-size:.875rem;font-weight:400;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.eps-text-field--outlined{border-radius:.1875rem;border:1px solid var(--eps-text-field-outlined-border-color);padding:.625rem}.eps-text-field--outlined:focus{border-color:var(--eps-text-field-outlined-focus-border-color)}.eps-text-field::-webkit-input-placeholder{color:var(--eps-text-field-placeholder-color);font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-size:.875rem;font-weight:400;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.eps-text-field::-moz-placeholder{color:var(--eps-text-field-placeholder-color);font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-size:.875rem;font-weight:400;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.eps-text-field::-ms-input-placeholder{color:var(--eps-text-field-placeholder-color);font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-size:.875rem;font-weight:400;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.eps-text-field::placeholder{color:var(--eps-text-field-placeholder-color);font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-size:.875rem;font-weight:400;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.eps-theme-dark .eps-text-field{--eps-text-field-color:#b4b5b7;--eps-text-field-background-color:#34383c;--eps-text-field-placeholder-color:#7d7e82;--eps-text-field-outlined-border-color:#64666a;--eps-text-field-outlined-focus-border-color:#7d7e82}.e-app-import-export-content-layout{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;height:100%}.e-app-import-export-content-layout__container{-ms-flex-preferred-size:1075px;flex-basis:1075px}.e-app-export-complete__kit-content-title{margin:2.75rem 0 .625rem}.e-app-export-kit-content{--e-app-export-kit-content-title-color:#556068;--e-app-export-kit-content-description-color:#6d7882}.e-app-export-kit-content__checkbox{-ms-flex-negative:0;flex-shrink:0;-webkit-margin-end:.75rem;margin-inline-end:.75rem}.e-app-export-kit-content__title{color:var(--e-app-export-kit-content-title-color)}.e-app-export-kit-content__description{color:var(--e-app-export-kit-content-description-color);-webkit-margin-end:1.25rem;margin-inline-end:1.25rem}.e-app-export-kit-content__notice{margin-top:1rem}.e-app-export-kit-content__small-notice{font-style:italic;color:var(--e-app-export-kit-content-description-color)}.eps-theme-dark .e-app-export-kit-content{--e-app-export-kit-content-title-color:#e0e1e3;--e-app-export-kit-content-description-color:#b4b5b7}.e-app-import-export-kit-data{--e-app-import-export-kit-data-site-area-color:#556068;--e-app-import-export-kit-data-included-color:#a4afb7}.e-app-import-export-kit-data__included,.e-app-import-export-kit-data__site-area{margin-bottom:0}.e-app-import-export-kit-data__site-area{color:var(--e-app-import-export-kit-data-site-area-color);font-weight:700}.e-app-import-export-kit-data__included{color:var(--e-app-import-export-kit-data-included-color)}.eps-theme-dark .e-app-import-export-kit-data{--e-app-import-export-kit-data-site-area-color:#b4b5b7;--e-app-import-export-kit-data-included-color:#7d7e82}.e-app-import-resolver{--e-app-import-resolver-panel-header-background-color:#fff;--e-app-import-resolver-panel-body-background-color:hsla(0,0%,100%,0.5);--e-app-import-resolver-conflicts-asset-border-color:#c2cbd2;--e-app-import-resolver-conflicts-asset-inactive-color:#a4afb7;padding-bottom:1.25rem}.e-app-import-resolver__notice{margin-bottom:1.25rem}.e-app-import-resolver__panel,.e-app-import-resolver__panel:hover{background-color:initial}.e-app-import-resolver__panel-header{background-color:var(--e-app-import-resolver-panel-header-background-color)}.e-app-import-resolver__panel-body{background-color:var(--e-app-import-resolver-panel-body-background-color)}.e-app-import-resolver-conflicts__container{-webkit-box-shadow:0 2px 3px 1px var(--color-box-shadow-color);box-shadow:0 2px 3px 1px var(--color-box-shadow-color)}.e-app-import-resolver-conflicts__checkbox{-ms-flex-negative:0;flex-shrink:0;-webkit-margin-end:.75rem;margin-inline-end:.75rem}.e-app-import-resolver-conflicts__title{line-height:1}.e-app-import-resolver-conflicts__asset:not(:first-child){-webkit-border-start:2px solid var(--e-app-import-resolver-conflicts-asset-border-color);border-inline-start:2px solid var(--e-app-import-resolver-conflicts-asset-border-color);-webkit-padding-start:1rem;padding-inline-start:1rem;-webkit-margin-start:1rem;margin-inline-start:1rem}.e-app-import-resolver-conflicts__asset:not(.active){color:var(--e-app-import-resolver-conflicts-asset-inactive-color)}.e-app-import-resolver-conflicts__edit-template{-webkit-margin-start:.3125rem;margin-inline-start:.3125rem}.eps-theme-dark .e-app-import-resolver{--e-app-import-resolver-panel-header-background-color:#4c4f56;--e-app-import-resolver-panel-body-background-color:rgba(0,0,0,0.05);--e-app-import-resolver-conflicts-asset-border-color:#64666a;--e-app-import-resolver-conflicts-asset-inactive-color:#7d7e82}.eps-panel{--eps-panel-header-background-color:#fff;--eps-panel-body-background-color:hsla(0,0%,100%,0.5)}.eps-panel,.eps-panel:hover{background-color:initial}.eps-panel__header{background-color:var(--eps-panel-header-background-color);border-radius:.1875rem}.eps-panel__body{background-color:var(--eps-panel-body-background-color);border-radius:0 0 .1875rem .1875rem}.eps-theme-dark .eps-panel{--eps-panel-header-background-color:#404349;--eps-panel-body-background-color:rgba(64,67,73,0.5)}.e-app-export-kit{padding-bottom:1.25rem}.e-app-export-kit-information{margin-top:1.25rem}.e-app-export-kit-information__field-header{margin-bottom:.625rem}.e-app-export-kit-information__label{margin:0}.e-app-export-kit-information__info-icon{-webkit-margin-start:.625rem;margin-inline-start:.625rem}.e-app-export-kit-info-modal__icon{-webkit-padding-start:.625rem;padding-inline-start:.625rem}.e-app-export-kit-info-modal__heading{margin-bottom:1.25rem}.e-app-import-export-info-modal__section:not(:first-child){margin-top:1.875rem}.e-app-import-export-info-modal__heading{margin-bottom:1.25rem}:root{--eps-badge-background-color:#fff}.eps-theme-dark{--eps-badge-background-color:#404349}.eps-badge{display:inline-block;background:var(--eps-badge-background-color);padding:0 .5rem;line-height:1.8;-webkit-box-shadow:0 3px 6px var(--color-box-shadow-color);box-shadow:0 3px 6px var(--color-box-shadow-color);border-radius:4px;font-size:.75rem}.eps-badge--sm{font-size:.625rem;border-radius:3px;padding:0 .3125rem;line-height:1.5}.eps-collapse__title{cursor:pointer;padding:.3125rem 0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;width:100%;background:transparent;border:none;color:inherit}.eps-collapse__title:focus{outline:none}.eps-collapse__icon{-webkit-transition:all .15s;-o-transition:all .15s;transition:all .15s;-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.eps-collapse__content{margin-top:.625rem;display:none}.eps-collapse[data-open] .eps-collapse__content{display:block}.eps-collapse[data-open] .eps-collapse__icon{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.e-kit-library-bottom-promotion{--e-kit-library-bottom-promotion-color:tints(600)}.eps-theme-dark .e-kit-library-bottom-promotion{--e-kit-library-bottom-promotion-color:dark-tints(400)}.e-kit-library-bottom-promotion{width:100%;text-align:center;margin-top:1.875rem;color:var(--e-kit-library-bottom-promotion-color)}.e-kit-library__error-screen{margin-top:2.75rem}.e-kit-library__error-screen-title{margin-top:2.75rem;margin-bottom:0}.e-kit-library__error-screen-description{margin-top:1.5rem;color:#a4afb7;text-align:center;max-width:520px}.e-kit-library__kit-favorite-actions{padding:.3125rem;-webkit-transition:all .3s;-o-transition:.3s all;transition:all .3s;border-radius:4px}.e-kit-library__kit-favorite-actions--active{color:#b01b1b}.e-kit-library__kit-favorite-actions--loading{opacity:1%;cursor:default}.e-kit-library__kit-favorite-actions:hover{background-color:rgba(176,27,27,.1)}.e-kit-library__filter-indication{margin-top:1.5rem}.e-kit-library__filter-indication,.e-kit-library__filter-indication-text{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.e-kit-library__filter-indication-text{margin-bottom:0}.e-kit-library__filter-indication-badge{margin-right:.3125rem;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.e-kit-library__filter-indication-badge-remove{margin-right:.3125rem;font-size:.875rem}.e-kit-library__filter-indication-button{margin-right:1.5rem}#eps-app-header-btn-apply,#eps-app-header-btn-connect,#eps-app-header-btn-promotion{margin-right:.625rem;margin-left:.625rem}.e-kit-library__apply-button{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:.3125rem}.e-kit-library__kit-item{--e-kit-library-kit-item-overlay-promotion-button-background-color:#fcfcfc}.eps-theme-dark .e-kit-library__kit-item{--e-kit-library-kit-item-overlay-promotion-button-background-color:#404349}.e-kit-library__kit-item-overlay{height:100%}.e-kit-library__kit-item-overlay>:first-child{-webkit-box-flex:1;-ms-flex:1;flex:1}.e-kit-library__kit-item-overlay-overview-button{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;color:#fff;height:100%;width:100%}.e-kit-library__kit-item-overlay-overview-button>i{font-size:2rem;margin-bottom:5px}.e-kit-library__kit-item-overlay-overview-button>span{font-size:.9rem}.e-kit-library__kit-item-overlay-promotion-button{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%;background:#fff;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding:10px;font-size:13px;color:#93003f;background:var(--e-kit-library-kit-item-overlay-promotion-button-background-color)}.e-kit-library__kit-item-overlay-promotion-button>*{margin:0 3px}.e-kit-library__kit-item-subscription-plan-badge{position:absolute;top:0;right:0;margin:.3125rem;color:#fff;text-transform:uppercase}:root{--e-kit-library-header-back-border:1px solid #f1f3f5;--e-kit-library-header-back-color:#a4afb7}.eps-theme-dark{--e-kit-library-header-back-border:1px solid #64666a;--e-kit-library-header-back-color:#b4b5b7}.e-kit-library__header-back{color:var(--e-kit-library-header-back-color);padding-left:1.25rem;padding-right:.3125rem;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:100%;border-left:var(--e-kit-library-header-back-border)}.e-kit-library__header-back-container{-webkit-box-flex:1;-ms-flex:1;flex:1;height:100%}.e-kit-library__header-back .eps-icon{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.e-kit-library__page-loader{width:100%;height:100%;display:grid;place-items:center;font-size:1.85rem;color:#a4afb7}.eps-search-input{--eps-search-input-background-color:#fcfcfc;--eps-search-input-background-color-focus:#fff;--eps-search-input-color:#556068;--eps-search-input-placeholder-color:#a4afb7}.eps-theme-dark .eps-search-input{--eps-search-input-background-color:#404349;--eps-search-input-background-color-focus:#4c4f56;--eps-search-input-color:#b4b5b7;--eps-search-input-placeholder-color:#b4b5b7}.eps-search-input{width:100%;font-size:.9375rem;padding:.625rem 2.75rem;border:none;background:var(--eps-search-input-background-color);outline:none;color:var(--eps-search-input-color);line-height:1;height:2.75rem}.eps-search-input--sm{font-size:.8125rem;padding:.5rem 1.875rem}.eps-search-input:focus{background:var(--eps-search-input-background-color-focus)}.eps-search-input::-webkit-input-placeholder{color:var(--eps-search-input-placeholder-color);font-style:italic}.eps-search-input::-moz-placeholder{color:var(--eps-search-input-placeholder-color);font-style:italic}.eps-search-input::-ms-input-placeholder{color:var(--eps-search-input-placeholder-color);font-style:italic}.eps-search-input::placeholder{color:var(--eps-search-input-placeholder-color);font-style:italic}.eps-search-input__container{position:relative}.eps-search-input__icon{font-size:1.25rem;padding:.625rem;color:#a4afb7;position:absolute;top:0;inset-inline-start:0;height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.eps-search-input__icon--sm{font-size:.75rem}.eps-search-input__clear-icon{font-size:.875rem;padding:.625rem;color:#a4afb7;position:absolute;top:0;left:0;height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.eps-search-input__clear-icon--sm{font-size:.6875rem}:root{--eps-sort-select-select-background-color:#fcfcfc;--eps-sort-select-select-color:#556068;--eps-sort-select-button-background-color:#fcfcfc;--eps-sort-select-button-border:1px solid #f1f3f5}.eps-theme-dark{--eps-sort-select-select-background-color:#404349;--eps-sort-select-select-color:#b4b5b7;--eps-sort-select-button-background-color:#404349;--eps-sort-select-button-border:1px solid #26292c}.eps-sort-select{width:100%;font-size:.9375rem;display:-webkit-box;display:-ms-flexbox;display:flex}.eps-sort-select__select-wrapper{-webkit-box-flex:1;-ms-flex:1;flex:1;position:relative}.eps-sort-select__select-wrapper:after{content:"\e8ad";font-family:eicons;position:absolute;left:.625rem;top:0;bottom:0;color:#a4afb7;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;pointer-events:none}.eps-sort-select__select{width:100%;padding:.625rem;border:none;background:var(--eps-sort-select-select-background-color);outline:none;color:var(--eps-sort-select-select-color);line-height:1;cursor:pointer;height:2.75rem;-moz-appearance:none;appearance:none;-webkit-appearance:none;border-radius:0}.eps-sort-select__button{padding:.75rem;background:var(--eps-sort-select-button-background-color);border-right:var(--eps-sort-select-button-border);line-height:1;color:#a4afb7}.e-kit-library__tags-filter{--e-kit-library-tags-filter-list-search-background-color:#fff}.eps-theme-dark .e-kit-library__tags-filter{--e-kit-library-tags-filter-list-search-background-color:#404349}.e-kit-library__tags-filter{margin-top:2.75rem}.e-kit-library__tags-filter-list{margin-bottom:2.75rem}.e-kit-library__tags-filter-list .eps-collapse__title{padding-right:1.875rem;padding-left:1.875rem;text-transform:uppercase}.e-kit-library__tags-filter-list .eps-collapse__content{margin:.3125rem 1.875rem}.e-kit-library__tags-filter-list-container{max-height:250px;overflow:auto}.e-kit-library__tags-filter-list-search{margin-bottom:.625rem}.e-kit-library__tags-filter-list-search .eps-search-input{background:var(--e-kit-library-tags-filter-list-search-background-color)}.e-kit-library__tags-filter-list-item{padding:.625rem 0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-weight:500}.e-kit-library__tags-filter-list-item input{margin-left:.3125rem}.e-kit-library #eps-app-header-btn-refetch{padding:0}.e-kit-library-header-info-modal-container{margin-bottom:2.75rem}.e-kit-library__tooltip{padding:5px 12px;color:#fff;background-color:#26292c;font-size:10px}.e-kit-library__tooltip:before{border-bottom-color:#26292c}.e-kit-library__index-layout-container{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;overflow-y:auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.e-kit-library__index-layout-top-area{padding:1.875rem 2.75rem;position:sticky;top:-1px;width:100%;z-index:2;background-color:var(--app-background-color);gap:1.5rem}.e-kit-library__index-layout-top-area-search,.e-kit-library__index-layout-top-area-sort{min-width:265px}.e-kit-library__index-layout-top-area-search{-webkit-box-flex:1;-ms-flex:1;flex:1}.e-kit-library__index-layout-main{padding-top:0;padding-bottom:1.5rem;overflow-y:hidden;height:auto;-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.e-kit-library__tags-filter{--e-kit-library-item-sidebar-header-title-color:#6d7882;--e-kit-library-item-sidebar-description-color:#6d7882;--e-kit-library-item-information-text-color:#6d7882}.eps-theme-dark .e-kit-library__tags-filter{--e-kit-library-item-sidebar-header-title-color:#b4b5b7;--e-kit-library-item-sidebar-description-color:#b4b5b7;--e-kit-library-item-information-text-color:#b4b5b7}.e-kit-library__item-sidebar{padding:1.5rem 1.875rem}.e-kit-library__item-sidebar-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.e-kit-library__item-sidebar-header-title{color:var(--e-kit-library-item-sidebar-header-title-color);margin-bottom:0}.e-kit-library__item-sidebar-thumbnail{margin-top:1.5rem;-webkit-box-shadow:0 4px 10px var(--color-box-shadow-color);box-shadow:0 4px 10px var(--color-box-shadow-color)}.e-kit-library__item-sidebar-description{margin-top:1.5rem;color:var(--e-kit-library-item-sidebar-description-color)}.e-kit-library__item-sidebar-collapse-tags{margin-top:2.75rem}.e-kit-library__item-sidebar-collapse-info{margin-top:1.875rem}.e-kit-library__item-sidebar-tags-container{gap:.625rem}.e-kit-library__item-information-text{font-size:.8125rem;color:var(--e-kit-library-item-information-text-color);margin-bottom:.3125rem}.e-kit-library{--e-kit-library-content-overview-group-title-color:#6d7882}.eps-theme-dark .e-kit-library{--e-kit-library-content-overview-group-title-color:#b4b5b7}.e-kit-library__content-overview-group-item{margin-bottom:2.75rem}.e-kit-library__content-overview-group-title{margin-bottom:1.875rem;color:var(--e-kit-library-content-overview-group-title-color)}.e-kit-library__preview-responsive-controls{width:auto}.e-kit-library__preview-responsive-controls-item{margin:0 .3125rem;color:#a4afb7;padding:.3125rem}.e-kit-library__preview-responsive-controls-item:hover{background:rgba(88,208,245,.1);border-radius:3px;-webkit-transition:all .3s;-o-transition:all .3s;transition:all .3s}.e-kit-library__preview-responsive-controls-item--active{color:#58d0f5}.e-kit-library__preview-loader{position:absolute;top:0;left:0;z-index:0}.e-kit-library__preview-iframe{border:none;-webkit-transition:all .3s;-o-transition:all .3s;transition:all .3s;-webkit-box-shadow:0 4px 10px var(--color-box-shadow-color);box-shadow:0 4px 10px var(--color-box-shadow-color)}.e-kit-library__preview-iframe-container{overflow-y:auto;position:relative;z-index:1}.e-app-collapse{--e-app-collapse-icon-color:#6d7882}.e-app-collapse-toggle{position:relative}.e-app-collapse-toggle--active{cursor:pointer}.e-app-collapse-toggle__icon{color:var(--e-app-collapse-icon-color);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;font-size:.875rem;position:absolute;top:50%;right:var(--e-app-collapse-toggle-icon-spacing);-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.e-app-collapse-toggle__icon:before{-webkit-transition:all .2s linear;-o-transition:all .2s linear;transition:all .2s linear}.e-app-collapse-content{display:none}.e-app-collapse--opened .e-app-collapse-toggle__icon:before{-webkit-transform:rotate(-180deg);-ms-transform:rotate(-180deg);transform:rotate(-180deg)}.e-app-collapse--opened .e-app-collapse-content{display:block}[dir=rtl] .e-app-collapse-toggle__icon{right:auto;left:var(--e-app-collapse-toggle-icon-spacing)}.eps-theme-dark .e-app-collapse{--e-app-collapse-icon-color:#b4b5b7}.e-app-import-plugins{--e-app-import-plugins-selection-section-heading-color:#6d7882;padding-bottom:1.5rem}.e-app-import-plugins__section{margin-top:1.875rem}.e-app-import-plugins__section-heading{color:var(--e-app-import-plugins-selection-section-heading-color);margin-bottom:1rem}.e-app-import-plugins__versions-notice{margin-bottom:.75rem}.eps-theme-dark .e-app-import-plugins{--e-app-import-plugins-selection-section-heading-color:#b4b5b7}.eps-table{--eps-table-body-color:#556068;--eps-table-body-background-color:#fff;border-spacing:0 2px;table-layout:fixed;width:100%}.eps-table__checkboxes-column{width:1.875rem}.eps-table__checkbox{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-negative:0;flex-shrink:0}.eps-table__cell{padding:1rem}.eps-table__head .eps-table__cell{text-align:start}.eps-table__body .eps-table__row{background-color:var(--eps-table-body-background-color)}.eps-table__body .eps-table__cell:first-child{border-radius:.1875rem 0 0 .1875rem}.eps-table__body .eps-table__cell:last-child{border-radius:0 .1875rem .1875rem 0}.eps-table--selection .eps-table__cell:first-child{-webkit-padding-end:0;padding-inline-end:0}.eps-theme-dark .eps-table{--eps-table-body-color:#b4b5b7;--eps-table-body-background-color:#404349}[dir=rtl] .eps-table__body [dir=rtl] .eps-table__cell:first-child{border-radius:0 .1875rem .1875rem 0}[dir=rtl] .eps-table__body [dir=rtl] .eps-table__cell:last-child{border-radius:.1875rem 0 0 .1875rem}.e-app-import-plugins-pro-banner{--e-app-import-plugins-pro-banner-heading-color:#556068;--e-app-import-plugins-pro-banner-description-color:#6d7882;margin-bottom:1.875rem}.e-app-import-plugins-pro-banner__heading{color:var(--e-app-import-plugins-pro-banner-heading-color);margin-bottom:.625rem}.e-app-import-plugins-pro-banner__description{color:var(--e-app-import-plugins-pro-banner-description-color);margin-bottom:0}.eps-theme-dark .e-app-import-plugins-pro-banner{--e-app-import-plugins-pro-banner-heading-color:#7d7e82;--e-app-import-plugins-pro-banner-description-color:#b4b5b7}.e-app-export-plugins,.e-app-import-content{padding-bottom:1.25rem}.e-app-import-content__plugins-notice{margin-bottom:1.25rem}.e-app-import-plugins-activation__installing-plugins{padding:.625rem 0}.e-app-import-plugins-activation__plugin-name{-webkit-margin-start:.5rem;margin-inline-start:.5rem}.e-app-import-plugins-activation__plugin-status-item{margin-bottom:.75rem}.e-app-import-export-plugins-table__cell-content{margin-bottom:0;text-transform:capitalize}.e-app-import-export-loader{--e-app-import-export-loader-color:#c2cbd2;color:var(--e-app-import-export-loader-color);font-size:50px}.e-app-import-export-loader.eicon-loading{font-size:1.85rem}.e-app-import-export-loader--absolute-center{position:absolute;top:50%;left:50%;-webkit-transform:translateX(-50%) translateY(-50%);-ms-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.eps-theme-dark .e-app-import-export-loader{--e-app-import-export-loader-color:#64666a}.e-app-import-export-message-banner{--e-app-import-export-message-banner-heading-color:#556068;--e-app-import-export-message-banner-description-color:#6d7882;margin-bottom:1.875rem}.e-app-import-export-message-banner__heading{color:var(--e-app-import-export-message-banner-heading-color);margin-bottom:.625rem}.e-app-import-export-message-banner__description{color:var(--e-app-import-export-message-banner-description-color);margin-bottom:0}.eps-theme-dark .e-app-import-export-message-banner{--e-app-import-export-message-banner-heading-color:#7d7e82;--e-app-import-export-message-banner-description-color:#b4b5b7}.e-app-import-connect-pro-notice,.e-app-import-failed-plugins-notice{margin-bottom:1.25rem}.e-onboarding{font-family:DM Sans,Roboto,sans-serif}.e-onboarding .eps-app__main{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;overflow-y:auto}.e-onboarding__content{max-width:1135px;padding:initial;margin:2.75rem;overflow-y:initial}.e-onboarding__checkbox-label{display:-webkit-box;display:-ms-flexbox;display:flex;line-height:18px;margin-bottom:27px}.e-onboarding__checkbox-input{-webkit-margin-end:14px;margin-inline-end:14px;width:16px;height:16px;border-color:#69727d;border-radius:2px}.e-onboarding__checkbox-input:checked{background-color:#525861}.e-onboarding__checkbox-input:checked:after{margin-bottom:15%;width:6px;height:9px;border-width:0 2px 2px 0}.e-onboarding__feature-list{margin-bottom:40px}.e-onboarding__text-input{font-size:14px;width:325px;padding:12px 16px;color:#69727d;border:1px solid #9ea5ad}.e-onboarding__text-input:focus-visible{outline:initial;border:1px solid #3a3f45}.e-onboarding__text-input::-webkit-input-placeholder{color:#c2c7cc}.e-onboarding__text-input::-moz-placeholder{color:#c2c7cc}.e-onboarding__text-input::-ms-input-placeholder{color:#c2c7cc}.e-onboarding__text-input::placeholder{color:#c2c7cc}.e-onboarding__footnote{margin-top:24px;width:325px;text-align:center}.e-onboarding__footnote a{text-decoration:underline;color:#3a3f45}#e-app~#__wp-uploader-id-2 .media-modal{max-width:1024px;max-height:768px;margin:auto}.e-onboarding__page-content{margin-bottom:70px}.e-onboarding__page-content-start{max-width:675px;text-align:start;-ms-flex-preferred-size:555px;flex-basis:555px;-ms-flex-item-align:start;align-self:start}.e-onboarding__page-content-end{min-width:440px;max-width:540px}.e-onboarding__page-content-end img{width:100%}.e-onboarding__page-content-section-title{font-family:"DM Serif Display",serif;font-size:36px;font-weight:700;color:#0c0d0e}.e-onboarding__page-content-section-text{font-size:18px;color:#3a3f45}.e-onboarding__header-logo .eps-app__logo{background-color:#0c0d0e;color:#fff;width:1.3rem;height:1.3rem;line-height:1.3rem;font-size:.48rem}.e-onboarding__header-logo .eps-app__logo:not(:last-child){-webkit-margin-end:7px;margin-inline-end:7px}.e-onboarding__header-logo img{width:104px}.e-onboarding__header .eps-app__header-btn{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:13px}.e-onboarding__header .eps-app__header-btn .eps-icon:not(:last-child){-webkit-margin-end:7px;margin-inline-end:7px}.e-onboarding__header .eps-button{color:#0c0d0e}.e-onboarding__header .eps-button__go-pro-btn{background-color:#871a40;color:#fff;padding:4px 8px;border-radius:4px;font-weight:700;font-size:12px;-webkit-transition:.5s;-o-transition:.5s;transition:.5s}.e-onboarding__header .eps-button__go-pro-btn:hover{background:#b22254}.e-onboarding__go-pro{width:288px;font-size:12px}.e-onboarding__go-pro-title{font-size:18px;font-weight:700;color:#871a40}.e-onboarding__go-pro-cta{display:inline-block;color:#871a40;padding:9px;border:1px solid #871a40}.e-onboarding__go-pro-cta.e-onboarding__button{font-size:14px}.e-onboarding__go-pro-paragraph:not(:last-child){margin-bottom:20px}.e-onboarding__go-pro-already-have{text-decoration:underline}.e-onboarding__progress-bar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-bottom:125px}.e-onboarding__progress-bar-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#69727d;font-size:.75rem}.e-onboarding__progress-bar-item-icon{display:inline-block;font-family:"DM Serif Display",serif;text-align:center;width:1.1rem;height:1.1rem;line-height:1rem;font-size:.75rem;font-weight:700;border-radius:50%;border:1px solid #69727d;-webkit-margin-end:8px;margin-inline-end:8px;-ms-flex-negative:0;flex-shrink:0}.e-onboarding__progress-bar-item:not(:last-child){-webkit-margin-end:22px;margin-inline-end:22px}.e-onboarding__progress-bar-item:not(:last-child):after{font-family:eicons;-webkit-margin-start:22px;margin-inline-start:22px;content:"\e89e"}.e-onboarding__progress-bar-item--active{color:#040080}.e-onboarding__progress-bar-item--active .e-onboarding__progress-bar-item-icon,.e-onboarding__progress-bar-item--completed .e-onboarding__progress-bar-item-icon{color:#fff;border-color:#040080;background-color:#040080}.e-onboarding__progress-bar-item--completed,.e-onboarding__progress-bar-item--skipped{cursor:pointer}.e-onboarding__progress-bar-item--skipped .e-onboarding__progress-bar-item-icon{color:#fff;background-color:#69727d}.e-onboarding__button{font-size:18px;cursor:pointer}.e-onboarding__button-action{color:#46f2b6;background-color:#040080;width:325px;padding:15px;text-align:center}.e-onboarding__button-skip{color:#6a727c;padding:15px 29px}.e-onboarding__button--disabled{pointer-events:none;background-color:#d5d8dc;color:#9ea5ad}.e-onboarding__button--disabled:hover{cursor:progress}.e-onboarding__button--processing{pointer-events:none;-webkit-filter:brightness(90%);filter:brightness(90%)}.e-onboarding__card{border:1px solid #3a3f45;border-radius:8px;padding:40px;background-color:#fff;cursor:pointer;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.e-onboarding__card-image,.e-onboarding__card-text{width:345px}.e-onboarding__card-image{margin-bottom:44px}.e-onboarding__card-text{font-size:20px;font-weight:700;text-align:center;color:#0c0d0e}.e-onboarding__card:hover{-webkit-box-shadow:4px 4px 0 0 #000;box-shadow:4px 4px 0 0 #000}.e-onboarding__card:active{-webkit-box-shadow:initial;box-shadow:none}.e-onboarding__checklist{-webkit-padding-start:0;padding-inline-start:0}.e-onboarding__checklist-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:12px;margin-bottom:12px}.e-onboarding__checklist-item .eicon-check-circle{-webkit-margin-end:9px;margin-inline-end:9px;font-size:1
1
+ /*! elementor - v3.7.8 - 03-10-2022 */
2
  @import "//fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap";@import "//fonts.googleapis.com/css2?family=DM%20Sans&display=swap";@import "//fonts.googleapis.com/css2?family=Source%20Serif%20Pro&display=swap";.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media screen and (min-width:480px){.text-start-sm{text-align:start}}@media screen and (min-width:480px){.text-center-sm{text-align:center}}@media screen and (min-width:480px){.text-end-sm{text-align:end}}@media screen and (min-width:768px){.text-start-md{text-align:start}}@media screen and (min-width:768px){.text-center-md{text-align:center}}@media screen and (min-width:768px){.text-end-md{text-align:end}}@media screen and (min-width:1025px){.text-start-lg{text-align:start}}@media screen and (min-width:1025px){.text-center-lg{text-align:center}}@media screen and (min-width:1025px){.text-end-lg{text-align:end}}@media screen and (min-width:1440px){.text-start-xl{text-align:start}}@media screen and (min-width:1440px){.text-center-xl{text-align:center}}@media screen and (min-width:1440px){.text-end-xl{text-align:end}}@media screen and (min-width:1600px){.text-start-xxl{text-align:start}}@media screen and (min-width:1600px){.text-center-xxl{text-align:center}}@media screen and (min-width:1600px){.text-end-xxl{text-align:end}}@-webkit-keyframes eps-animation-pop{0%{-webkit-transform:scale(.75);transform:scale(.75);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes eps-animation-pop{0%{-webkit-transform:scale(.75);transform:scale(.75);opacity:0}to{-webkit-transform:scale(1);transform:scale(1);opacity:1}}.eps-button{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;--button-line-height:16px;--button-padding-y:0.5em;--button-padding-x:1.5em;--button-primary-background-color:#39b54a;--button-primary-hover-background-color:#33a242;--button-primary-active-background-color:#35a945;--button-primary-color:#fff;--button-secondary-background-color:#a4afb7;--button-secondary-hover-background-color:#96a2ac;--button-secondary-active-background-color:#9ba7b0;--button-secondary-color:#fff;--button-danger-background-color:#b01b1b;--button-danger-hover-background-color:#9a1818;--button-danger-active-background-color:#a31919;--button-danger-color:#fff;--button-cta-background-color:#93003f;--button-cta-hover-background-color:#7a0034;--button-cta-active-background-color:#840038;--button-cta-color:#fff;--button-link-background-color:#58d0f5;--button-link-hover-background-color:#40c9f4;--button-link-active-background-color:#4accf4;--button-link-color:#fff;--button-disabled-background-color:#c2cbd2;--button-disabled-hover-background-color:#b3bec7;--button-disabled-active-background-color:#b9c3cc;--button-disabled-color:#fff;color:var(--button-background-color,currentColor);font-size:var(--button-font-size,inherit);font-weight:500;line-height:var(--button-line-height);cursor:pointer}.eps-button:active{--button-background-color:var(--button-active-background-color,transparent)}.eps-button:hover{--button-background-color:var(--button-hover-background-color)}.eps-theme-dark .eps-button{--button-primary-background-color:#39b54a;--button-primary-color:#fff;--button-primary-hover-background-color:#33a242;--button-primary-active-background-color:#35a945;--button-secondary-background-color:#b4b5b7;--button-secondary-color:#fff;--button-secondary-hover-background-color:#a7a8ab;--button-secondary-active-background-color:#acadb0;--button-cta-background-color:#93003f;--button-cta-hover-background-color:#7a0034;--button-cta-active-background-color:#840038;--button-cta-color:#fff;--button-link-background-color:#58d0f5;--button-link-hover-background-color:#40c9f4;--button-link-active-background-color:#4accf4;--button-link-color:#fff;--button-disabled-background-color:#64666a;--button-disabled-hover-background-color:#58595d;--button-disabled-active-background-color:#5d5e62;--button-disabled-color:#fff}.eps-button--contained{padding:var(--button-padding-y) var(--button-padding-x);background-color:var(--button-background-color,transparent);border:1px solid var(--button-background-color)}.eps-button--contained,.eps-button--contained:hover{color:var(--button-color)}.eps-button--outlined{display:block;padding:var(--button-padding-y) var(--button-padding-x);border:1px solid var(--button-background-color)}.eps-button--contained,.eps-button--outlined{border-radius:.1875rem}.eps-button--underlined{text-decoration:underline}.eps-button--sm{--button-font-size:0.75rem;--button-line-height:14px}.eps-button--lg{--button-font-size:0.9375rem;--button-line-height:18px}.eps-button--primary{--button-color:var(--button-primary-color);--button-background-color:var(--button-primary-background-color);--button-hover-background-color:var(--button-primary-hover-background-color);--button-active-background-color:var(--button-primary-active-background-color)}.eps-button--secondary{--button-color:var(--button-secondary-color);--button-background-color:var(--button-secondary-background-color);--button-hover-background-color:var(--button-secondary-hover-background-color);--button-active-background-color:var(--button-secondary-active-background-color)}.eps-button--danger{--button-color:var(--button-danger-color);--button-background-color:var(--button-danger-background-color);--button-hover-background-color:var(--button-danger-hover-background-color);--button-active-background-color:var(--button-danger-active-background-color)}.eps-button--cta{--button-color:var(--button-cta-color);--button-background-color:var(--button-cta-background-color);--button-hover-background-color:var(--button-cta-hover-background-color);--button-active-background-color:var(--button-cta-active-background-color)}.eps-button--link{--button-color:var(--button-link-color);--button-background-color:var(--button-link-background-color);--button-hover-background-color:var(--button-link-hover-background-color);--button-active-background-color:var(--button-link-active-background-color)}.eps-button--disabled,.eps-button[disabled]{--button-color:var(--button-disabled-color);--button-background-color:var(--button-disabled-background-color);--button-hover-background-color:var(--button-disabled-hover-background-color);--button-active-background-color:var(--button-disabled-active-background-color);cursor:default}:root{--app-background-color:#f1f3f5;--app-box-shadow-color:rgba(var(--box-shadow-color,rgba(0,0,0,0.15)),0.2);--app-header-background-color:#fff;--app-header-color:#495157;--app-sidebar-background-color:hsla(0,0%,100%,0.5);--app-header-buttons-separator-color:#d5dadf;--app-header-buttons-color:#a4afb7;--app-lightbox-background-color:rgba(0,0,0,0.8)}.eps-theme-dark{--app-background-color:#34383c;--app-box-shadow-color:rgba(var(--box-shadow-color,rgba(0,0,0,0.15)),0.2);--app-header-background-color:#26292c;--app-header-color:#e0e1e3;--app-sidebar-background-color:#34383c;--app-header-buttons-separator-color:#64666a;--app-header-buttons-color:#b4b5b7;--app-lightbox-background-color:rgba(0,0,0,0.8)}:root{--text-muted:#d5dadf;--disabled:#c2cbd2;--danger:#b01b1b;--body-color:#6d7882;--hr-color:#d5dadf;--box-shadow-color:theme-colors(dark);--display-1-color:#495157;--display-2-color:#495157;--display-3-color:#6d7882;--display-4-color:#495157;--h1-color:#6d7882;--h2-color:#6d7882;--h3-color:#495157;--h4-color:#495157;--h5-color:#495157;--h6-color:#495157;--text-base-color:#495157;--text-xl-color:#495157;--text-lg-color:#495157;--text-sm-color:#495157;--text-xs-color:#495157;--text-xxs-color:#495157;--gray-800:#495157;--gray-700:#556068;--gray-600:#6d7882;--gray-500:#a4afb7;--gray-400:#c2cbd2;--gray-300:#d5dadf;--gray-200:#f1f3f5;--gray-100:#fcfcfc}.eps-theme-dark,:root{--light:#fff;--dark:#000;--info:#58d0f5;--cta:#93003f;--success:#39b54a;--warning:#fcb92c;--link-color:#58d0f5;--link-hover-color:#10bcf1}.eps-theme-dark{--text-muted:#7d7e82;--disabled:#64666a;--accent:#58d0f5;--danger:#f84343;--body-color:#e0e1e3;--body-bg:#fff;--hr-color:#4c4f56;--box-shadow-color:rgba(0,0,0,0.15);--display-1-color:#e0e1e3;--display-2-color:#e0e1e3;--display-3-color:#e0e1e3;--display-4-color:#e0e1e3;--h1-color:#e0e1e3;--h2-color:#e0e1e3;--h3-color:#e0e1e3;--h4-color:#e0e1e3;--h5-color:#e0e1e3;--h6-color:#e0e1e3;--text-base-color:#b4b5b7;--text-xl-color:#b4b5b7;--text-lg-color:#b4b5b7;--text-sm-color:#b4b5b7;--text-xs-color:#b4b5b7;--text-xxs-color:#b4b5b7;--gray-800:#26292c;--gray-700:#34383c;--gray-600:#404349;--gray-500:#4c4f56;--gray-400:#64666a;--gray-300:#7d7e82;--gray-200:#b4b5b7;--gray-100:#e0e1e3}*,:after,:before{-webkit-box-sizing:border-box;box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-size:.875rem;font-weight:400;line-height:1.5;color:var(--body-color);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:var(--body-bg)}::-moz-selection{background-color:rgba(147,0,63,.5)}::selection{background-color:rgba(147,0,63,.5)}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}h1,h2,h3,h4,h5,h6{font-size:100%;margin:0;padding:0;line-height:inherit;font-weight:400}p{margin-top:0}b,strong{font-weight:700}small{font-size:80%}a{--eps-link-color:$eps-link-color;color:var(--eps-link-color);background-color:transparent}a,a:active,a:focus,a:hover{text-decoration:none}a:focus,a:hover{--eps-link-color:$eps-link-hover-color}a:not([href]),a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:monospace;font-size:1em}figure{margin:0}img{border-style:none}img,svg{vertical-align:middle}svg{overflow:hidden}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}[hidden]{display:none!important}hr{border:0;border-bottom:1px solid var(--hr-color)}.eps-display-1{color:var(--display-1-color)}.eps-display-1,.eps-display-2{font-size:1.85rem;margin-top:.5rem;margin-bottom:.5rem}.eps-display-2{color:var(--display-2-color)}.eps-display-3{font-size:1.85rem;color:var(--display-3-color);margin-top:0;margin-bottom:1.25rem}.eps-display-4{font-size:1.85rem;color:var(--display-4-color);margin-top:.5rem;margin-bottom:.5rem}.eps-h1,h1{font-size:1.625rem;line-height:1;color:var(--h1-color);margin-bottom:1.25rem;font-weight:500}.eps-h2,h2{font-size:1.25rem;color:var(--h2-color);margin-bottom:1.25rem}.eps-h2,.eps-h3,h2,h3{line-height:1.2;margin-top:0;font-weight:500}.eps-h3,h3{font-size:1rem;color:var(--h3-color);margin-bottom:.5rem}.eps-h4,h4{font-size:.9375rem;color:var(--h4-color)}.eps-h4,.eps-h5,h4,h5{margin-top:0;margin-bottom:.5rem}.eps-h5,h5{font-size:.875rem;color:var(--h5-color)}.eps-h6,h6{font-size:.875rem;color:var(--h-6-color);margin-top:0;margin-bottom:.5rem;font-weight:700}.eps-text-xxs{line-height:1.2;color:var(--text-xxs-color)}.eps-text-xs,.eps-text-xxs{font-size:.75rem;font-weight:400}.eps-text-xs{line-height:1.5;color:var(--text-xs-color)}.eps-text{font-size:.875rem;color:var(--text-base-color)}.eps-text,.eps-text-sm{line-height:1.5;font-weight:400}.eps-text-sm{font-size:.8125rem;color:var(--text-sm-color)}.eps-text-lg{font-size:.9375rem;color:var(--text-lg-color)}.eps-text-lg,.eps-text-xl{line-height:1.5;font-weight:400}.eps-text-xl{font-size:1rem;color:var(--text-xl-color)}.video-wrapper{position:relative;padding-bottom:56.25%;height:0}.video-wrapper iframe{position:absolute;top:0;left:0;width:100%;height:100%}.eps-separator{margin-bottom:2.75rem}.eps-theme-dark{--e-app-back-button-color:#b4b5b7}.back-button,.e-app-back-button{--button-background-color:var(--e-app-back-button-color,#a4afb7);margin-bottom:1.5rem}.back-button .eps-icon,.e-app-back-button .eps-icon{-webkit-margin-end:.3125rem;margin-inline-end:.3125rem}.eps-theme-dark{--input-border-color:--hr-color}.eps-input{border:1px solid var(--hr-color);border-radius:.1875rem;background:transparent;color:inherit;height:1.875rem;padding:0 .3125rem}.eps-input--block{width:100%}.eps-app{height:100vh;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;color:var(--body-color);background-color:var(--app-background-color);position:absolute;border-radius:0;-webkit-box-shadow:2px 8px 23px 3px var(--color-box-shadow-color);box-shadow:2px 8px 23px 3px var(--color-box-shadow-color);overflow:hidden;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;max-width:100%}.eps-app,.eps-app__lightbox{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%}.eps-app__lightbox{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;position:fixed;height:100%;background-color:var(--app-lightbox-background-color);z-index:1040;bottom:0;left:0}.eps-app__header{-ms-flex-negative:0;flex-shrink:0;font-size:.9375rem;color:var(--app-header-color);background-color:var(--app-header-background-color);-webkit-box-shadow:0 4px 10px var(--color-box-shadow-color);box-shadow:0 4px 10px var(--color-box-shadow-color);position:relative;z-index:3;height:3.125rem;padding:0 1rem}.eps-app__header-buttons{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;font-size:1.125rem}.eps-app__header-btn{--button-background-color:var(--app-header-buttons-color);-webkit-padding-start:1rem;padding-inline-start:1rem;font-size:1.125rem;line-height:1.25rem}.eps-app__header-btn:first-child{-webkit-border-start:1px solid var(--app-header-buttons-separator-color);border-inline-start:1px solid var(--app-header-buttons-separator-color)}.eps-app__header-btn:not(:first-child){-webkit-padding-end:1rem;padding-inline-end:1rem}.eps-app__logo-title-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.eps-app__logo{display:block;width:1.75rem;height:1.75rem;line-height:1.75rem;text-align:center;font-size:calc(.4 * 1.75rem);border-radius:50%;color:#fff;background-color:#93003f}.eps-app__logo:not(:last-child){-webkit-margin-end:.625rem;margin-inline-end:.625rem}.eps-app__title{font-size:.9375rem;font-weight:700;text-transform:uppercase;margin-bottom:0}.eps-app__main{display:-webkit-box;display:-ms-flexbox;display:flex;overflow:hidden;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.eps-app__sidebar{background-color:var(--app-sidebar-background-color);padding-top:1.25rem;width:30%;max-width:17.1875rem;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;overflow-y:auto;-webkit-box-shadow:2px 8px 23px 3px var(--color-box-shadow-color);box-shadow:2px 8px 23px 3px var(--color-box-shadow-color);z-index:4}.eps-app__content{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;position:relative;padding:2.75rem;height:100%;overflow-y:auto}.e-app-upload-file__input{display:none}.e-app-drop-zone{--e-app-drop-zone-icon-color:#c2cbd2;--e-app-drop-zone-text-color:#a4afb7;--e-app-drop-zone-secondary-text-color:#6d7882}.e-app-drop-zone__icon{margin-bottom:2.75rem;color:var(--e-app-drop-zone-icon-color);font-size:60px}.e-app-drop-zone__text{color:var(--e-app-drop-zone-text-color)}.e-app-drop-zone__secondary-text{color:var(--e-app-drop-zone-secondary-text-color)}.eps-theme-dark .e-app-drop-zone{--e-app-drop-zone-icon-color:#c2cbd2;--e-app-drop-zone-text-color:#b4b5b7;--e-app-drop-zone-secondary-text-color:#e0e1e3}:root{--info-toggle-color:#d5dadf;--info-toggle-hover-color:#a4afb7}.eps-theme-dark{--placeholder-filter:invert(0.8) sepia(1) saturate(0.2) hue-rotate(180deg) contrast(1.25) brightness(1.2);--info-toggle-color:#64666a;--info-toggle-hover-color:#b4b5b7}.e-site-part .eps-card__image{-webkit-filter:var(--placeholder-filter,none);filter:var(--placeholder-filter,none)}.e-site-part__info-toggle{color:var(--info-toggle-color)}.e-site-part__info-toggle:hover{--info-toggle-color:var(--info-toggle-hover-color)}.e-site-editor__header{margin-bottom:2.75rem;border-bottom:1px solid var(--hr-color)}:root{--e-elementor-loader-container-background-color:#f1f3f5;--e-elementor-loader-background-color:hsla(0,0%,100%,0.9)}.eps-theme-dark{--e-elementor-loader-container-background-color:#34383c;--e-elementor-loader-background-color:#4c4f56}.elementor-loading{background-color:var(--e-elementor-loader-container-background-color);height:100vh}.elementor-loader-wrapper{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:300px;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.elementor-loader{border-radius:50%;padding:40px;height:150px;width:150px;background-color:var(--e-elementor-loader-background-color);-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-box-shadow:2px 2px 20px 4px rgba(0,0,0,.02);box-shadow:2px 2px 20px 4px rgba(0,0,0,.02)}.elementor-loader-boxes{height:100%;width:100%;position:relative}.elementor-loader-box{position:absolute;background-color:#d5dadf;-webkit-animation:load 1.8s linear infinite;animation:load 1.8s linear infinite}.elementor-loader-box:first-of-type{width:20%;height:100%;left:0;top:0}.elementor-loader-box:not(:first-of-type){right:0;height:20%;width:60%}.elementor-loader-box:nth-of-type(2){top:0;-webkit-animation-delay:calc(1.8s / 4 * -1);animation-delay:calc(1.8s / 4 * -1)}.elementor-loader-box:nth-of-type(3){top:40%;-webkit-animation-delay:calc(1.8s / 4 * -2);animation-delay:calc(1.8s / 4 * -2)}.elementor-loader-box:nth-of-type(4){bottom:0;-webkit-animation-delay:calc(1.8s / 4 * -3);animation-delay:calc(1.8s / 4 * -3)}.elementor-loading-title{color:#a4afb7;text-align:center;text-transform:uppercase;margin-top:30px;letter-spacing:7px;text-indent:7px;font-size:10px;width:100%}@-webkit-keyframes load{0%{opacity:.3}50%{opacity:1}to{opacity:.3}}@keyframes load{0%{opacity:.3}50%{opacity:1}to{opacity:.3}}.eps-menu__title{margin-top:2.75rem;margin-bottom:1rem}.e-app-import{--e-app-import-back-to-library-color:#a4afb7;padding-bottom:1.25rem}.e-app-import__drop-zone{margin-top:1.25rem}.e-app-import__back-to-library{color:var(--e-app-import-back-to-library-color);margin-bottom:1.5rem}.e-app-import__back-to-library>i{-webkit-margin-end:.5rem;margin-inline-end:.5rem}.eps-theme-dark .e-app-import{--e-app-import-back-to-library-color:#b4b5b7}.e-site-editor__promotion-overlay__link{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%;height:100%;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;text-decoration:none}.e-site-editor__promotion-overlay__icon{font-size:1.25rem;color:#fff;margin-bottom:1rem}.e-app-import-export-wizard-step{--e-app-import-export-wizard-step-icon-color:#c2cbd2;--e-app-import-export-wizard-step-text-color:#a4afb7;--e-app-import-export-wizard-step-bottom-text-color:#a4afb7;height:100%;position:relative;text-align:center}.e-app-import-export-wizard-step__media-container{height:140px;margin:5.5rem 0 2.75rem}.e-app-import-export-wizard-step__icon{color:var(--e-app-import-export-wizard-step-icon-color);font-size:50px}.e-app-import-export-wizard-step__icon.eicon-loading{font-size:1.85rem}.e-app-import-export-wizard-step__heading{margin-bottom:1.5rem}.e-app-import-export-wizard-step__description,.e-app-import-export-wizard-step__info{color:var(--e-app-import-export-wizard-step-text-color)}.e-app-import-export-wizard-step__info{margin-top:1.5rem}.e-app-import-export-wizard-step__content{text-align:initial;margin-bottom:1.25rem}.e-app-import-export-wizard-step__notice{display:block;position:sticky;top:100%;color:var(--e-app-import-export-wizard-step-bottom-text-color);font-style:italic;margin-bottom:0}.eps-theme-dark .e-app-import-export-wizard-step{--e-app-import-export-wizard-step-icon-color:#64666a;--e-app-import-export-wizard-step-text-color:#b4b5b7;--e-app-import-export-wizard-step-bottom-text-color:#b4b5b7}.e-app-import-export-page-header{--e-app-import-export-page-header-border-bottom-color:#d5dadf;--e-app-import-export-page-header-heading-color:#6d7882;--e-app-import-export-page-header-description-color:#a4afb7;border-bottom:1px solid var(--e-app-import-export-page-header-border-bottom-color);margin-bottom:2.75rem}.e-app-import-export-page-header__content-wrapper{max-width:645px}.e-app-import-export-page-header__heading{color:var(--e-app-import-export-page-header-heading-color)}.e-app-import-export-page-header__description{color:var(--e-app-import-export-page-header-description-color);margin-top:1.25rem}.eps-theme-dark .e-app-import-export-page-header{--e-app-import-export-page-header-border-bottom-color:#4c4f56;--e-app-import-export-page-header-heading-color:#e0e1e3;--e-app-import-export-page-header-description-color:#e0e1e3}.e-app-wizard-footer{--e-app-wizard-footer-border-color:#d5dadf;padding:.5rem}.e-app-wizard-footer__separator{border-top:1px solid var(--e-app-wizard-footer-border-color)}.eps-theme-dark .e-app-wizard-footer{--e-app-wizard-footer-border-color:#4c4f56}.e-app-export-templates-features__locked{--e-app-export-templates-features-locked-color:#a4afb7;color:var(--e-app-export-templates-features-locked-color)}.eps-theme-dark .e-app-export-templates-features__locked{--e-app-export-templates-features-locked-color:#7d7e82}:root{--color-box-shadow-color:rgba(0,0,0,0.05)}.eps-theme-dark{--color-box-shadow-color:rgba(0,0,0,0.1)}:root{--card-background-color:hsla(0,0%,100%,0.5);--card-background-color-hover:#fff;--card-box-shadow:0 4px 10px var(--color-box-shadow-color);--card-header-footer-border:1px solid #f1f3f5;--card-header-footer-active-border:2px solid #e2e6ea;--card-headline-color:#6d7882;--card-figure-background-color:#f1f3f5;--card-image-overlay-background-color:rgba(0,0,0,0.2)}.eps-theme-dark{--card-background-color:#404349;--card-background-color-hover:rgba(76,79,86,0.8);--card-box-shadow:0 3px 6px var(--color-box-shadow-color);--card-header-footer-border:1px solid #34383c;--card-header-footer-active-border:1px solid #26292c;--card-headline-color:#e0e1e3;--card-figure-background-color:#34383c;--card-image-overlay-background-color:rgba(52,56,60,0.5)}.eps-card{background-color:var(--card-background-color);-webkit-box-shadow:0 4px 10px var(--color-box-shadow-color);box-shadow:0 4px 10px var(--color-box-shadow-color);border-radius:.1875rem;-webkit-transition:.3s;-o-transition:.3s;transition:.3s;font-size:.75rem}.eps-card__header{padding:.625rem;border-bottom:var(--card-header-footer-border);min-height:2.5rem;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.eps-card__header--padding{padding:var(--eps-card-header-padding)}.eps-card__headline{color:var(--card-headline-color);margin-bottom:0;font-weight:500;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;overflow:hidden;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap}.eps-card__body{padding:.625rem}.eps-card__body--padding{padding:var(--eps-card-body-padding)}.eps-card__figure{background-color:var(--card-figure-background-color);position:relative;padding-bottom:var(--card-image-aspect-ratio,95.6%);overflow:hidden;height:0}.eps-card__image{width:100%;-o-object-fit:contain;object-fit:contain;-o-object-position:top;object-position:top;position:absolute;top:0;left:0}.eps-card__image-overlay{position:absolute;top:0;background-color:var(--card-image-overlay-background-color);z-index:1;width:100%;height:100%;opacity:0;-webkit-transition:.3s;-o-transition:.3s;transition:.3s}.eps-card__image-overlay:hover{opacity:1}.eps-card__footer{padding:.625rem;border-top:var(--card-header-footer-border);font-size:.6875rem}.eps-card__footer--padding{padding:var(--eps-card-footer-padding)}.eps-card:hover{background-color:var(--card-background-color-hover)}:root{--menu-item-color:#6d7882;--menu-item-color-hover:#556068;--menu-item-background-color-active:#fff;--menu-item-icon-color:#a4afb7;--menu-item-action-button-color:#d5dadf}.eps-theme-dark{--menu-item-color:#e0e1e3;--menu-item-color-hover:#e0e1e3;--menu-item-background-color-active:#404349;--menu-item-icon-color:#b4b5b7;--menu-item-action-button-color:#64666a}.eps-menu-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;position:relative;-webkit-transition:.3s;-o-transition:.3s;transition:.3s;--action-button-opacity:0}.eps-menu-item:before{content:"";display:block;position:absolute;top:0;inset-inline-start:0;height:100%;width:var(--menu-item-pointer-width);background-color:#58d0f5}.eps-menu-item:hover{--action-button-opacity:1}.eps-menu-item--active,.eps-menu-item:hover{--menu-item-color:var(--menu-item-color-hover);--eps-link-color:var(--menu-item-color-hover);--menu-item-icon-color:#58d0f5}.eps-menu-item--active{background-color:var(--menu-item-background-color-active)}.eps-menu-item__link{padding:.5rem 1.875rem;min-height:2.75rem;font-size:.75rem;line-height:1.2;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:var(--menu-item-color);--eps-link-color:var(--menu-item-color)}.eps-menu-item__link:not(:last-child){-webkit-padding-end:0;padding-inline-end:0}.eps-menu-item__link .eps-icon{font-size:1.125rem;color:var(--menu-item-icon-color);-webkit-margin-end:.75rem;margin-inline-end:.75rem}.eps-menu-item__action-button{color:var(--menu-item-action-button-color);opacity:var(--action-button-opacity);padding:.625rem;-webkit-transition:.3s;-o-transition:.3s;transition:.3s;-webkit-margin-end:1.25rem;margin-inline-end:1.25rem}.eps-menu-item--active{--menu-item-pointer-width:0.3125rem;-webkit-box-shadow:0 3px 6px var(--color-box-shadow-color);box-shadow:0 3px 6px var(--color-box-shadow-color)}.eps-menu-item--active .eps-menu-item__link .eps-icon{color:#58d0f5}.eps-grid-container{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;width:100%}.eps-grid-container--no-wrap{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.eps-grid-container--wrap-reverse{-ms-flex-wrap:wrap-reverse;flex-wrap:wrap-reverse}.eps-grid-container--spacing{--grid-row-gutter:calc(-1 * calc(var(--grid-spacing-gutter) * (0.625rem / 10)));width:var(--grid-spacing-width);margin:var(--grid-row-gutter)}.eps-grid-container--spacing>.eps-grid-item{padding:var(--grid-spacing-gutter)}.eps-grid--direction-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.eps-grid--direction-row-reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.eps-grid--direction-column{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.eps-grid--direction-column-reverse{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.eps-grid--justify-stretch{-webkit-box-pack:stretch;-ms-flex-pack:stretch;justify-content:stretch}.eps-grid--justify-start{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.eps-grid--justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.eps-grid--justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.eps-grid--justify-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.eps-grid--justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.eps-grid--justify-space-evenly{-webkit-box-pack:space-evenly;-ms-flex-pack:space-evenly;justify-content:space-evenly}.eps-grid--align-content-stretch{-ms-flex-line-pack:stretch;align-content:stretch}.eps-grid--align-content-start{-ms-flex-line-pack:start;align-content:flex-start}.eps-grid--align-content-center{-ms-flex-line-pack:center;align-content:center}.eps-grid--align-content-end{-ms-flex-line-pack:end;align-content:flex-end}.eps-grid--align-content-space-between{-ms-flex-line-pack:justify;align-content:space-between}.eps-grid--align-items-start{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.eps-grid--align-items-center{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.eps-grid--align-items-end{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.eps-grid--align-items-baseline{-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.eps-grid--align-items-stretch{-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}.eps-grid-item--zero-min-width{min-width:0}@media screen and (min-width:480px){.eps-grid-item-sm{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%;-ms-flex-preferred-size:0;flex-basis:0}}@media screen and (min-width:768px){.eps-grid-item-md{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%;-ms-flex-preferred-size:0;flex-basis:0}}@media screen and (min-width:1025px){.eps-grid-item-lg{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%;-ms-flex-preferred-size:0;flex-basis:0}}@media screen and (min-width:1440px){.eps-grid-item-xl{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%;-ms-flex-preferred-size:0;flex-basis:0}}@media screen and (min-width:1600px){.eps-grid-item-xxl{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%;-ms-flex-preferred-size:0;flex-basis:0}}.eps-grid-item-xs-1{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(1 / 12 * 100%);-ms-flex-preferred-size:calc(1 / 12 * 100%);flex-basis:calc(1 / 12 * 100%)}.eps-grid-item-xs-2{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(2 / 12 * 100%);-ms-flex-preferred-size:calc(2 / 12 * 100%);flex-basis:calc(2 / 12 * 100%)}.eps-grid-item-xs-3{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(3 / 12 * 100%);-ms-flex-preferred-size:calc(3 / 12 * 100%);flex-basis:calc(3 / 12 * 100%)}.eps-grid-item-xs-4{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(4 / 12 * 100%);-ms-flex-preferred-size:calc(4 / 12 * 100%);flex-basis:calc(4 / 12 * 100%)}.eps-grid-item-xs-5{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(5 / 12 * 100%);-ms-flex-preferred-size:calc(5 / 12 * 100%);flex-basis:calc(5 / 12 * 100%)}.eps-grid-item-xs-6{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(6 / 12 * 100%);-ms-flex-preferred-size:calc(6 / 12 * 100%);flex-basis:calc(6 / 12 * 100%)}.eps-grid-item-xs-7{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(7 / 12 * 100%);-ms-flex-preferred-size:calc(7 / 12 * 100%);flex-basis:calc(7 / 12 * 100%)}.eps-grid-item-xs-8{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(8 / 12 * 100%);-ms-flex-preferred-size:calc(8 / 12 * 100%);flex-basis:calc(8 / 12 * 100%)}.eps-grid-item-xs-9{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(9 / 12 * 100%);-ms-flex-preferred-size:calc(9 / 12 * 100%);flex-basis:calc(9 / 12 * 100%)}.eps-grid-item-xs-10{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(10 / 12 * 100%);-ms-flex-preferred-size:calc(10 / 12 * 100%);flex-basis:calc(10 / 12 * 100%)}.eps-grid-item-xs-11{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(11 / 12 * 100%);-ms-flex-preferred-size:calc(11 / 12 * 100%);flex-basis:calc(11 / 12 * 100%)}.eps-grid-item-xs-12{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(12 / 12 * 100%);-ms-flex-preferred-size:calc(12 / 12 * 100%);flex-basis:calc(12 / 12 * 100%)}@media screen and (min-width:480px){.eps-grid-item-sm-1{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(1 / 12 * 100%);-ms-flex-preferred-size:calc(1 / 12 * 100%);flex-basis:calc(1 / 12 * 100%)}.eps-grid-item-sm-2{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(2 / 12 * 100%);-ms-flex-preferred-size:calc(2 / 12 * 100%);flex-basis:calc(2 / 12 * 100%)}.eps-grid-item-sm-3{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(3 / 12 * 100%);-ms-flex-preferred-size:calc(3 / 12 * 100%);flex-basis:calc(3 / 12 * 100%)}.eps-grid-item-sm-4{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(4 / 12 * 100%);-ms-flex-preferred-size:calc(4 / 12 * 100%);flex-basis:calc(4 / 12 * 100%)}.eps-grid-item-sm-5{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(5 / 12 * 100%);-ms-flex-preferred-size:calc(5 / 12 * 100%);flex-basis:calc(5 / 12 * 100%)}.eps-grid-item-sm-6{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(6 / 12 * 100%);-ms-flex-preferred-size:calc(6 / 12 * 100%);flex-basis:calc(6 / 12 * 100%)}.eps-grid-item-sm-7{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(7 / 12 * 100%);-ms-flex-preferred-size:calc(7 / 12 * 100%);flex-basis:calc(7 / 12 * 100%)}.eps-grid-item-sm-8{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(8 / 12 * 100%);-ms-flex-preferred-size:calc(8 / 12 * 100%);flex-basis:calc(8 / 12 * 100%)}.eps-grid-item-sm-9{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(9 / 12 * 100%);-ms-flex-preferred-size:calc(9 / 12 * 100%);flex-basis:calc(9 / 12 * 100%)}.eps-grid-item-sm-10{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(10 / 12 * 100%);-ms-flex-preferred-size:calc(10 / 12 * 100%);flex-basis:calc(10 / 12 * 100%)}.eps-grid-item-sm-11{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(11 / 12 * 100%);-ms-flex-preferred-size:calc(11 / 12 * 100%);flex-basis:calc(11 / 12 * 100%)}.eps-grid-item-sm-12{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(12 / 12 * 100%);-ms-flex-preferred-size:calc(12 / 12 * 100%);flex-basis:calc(12 / 12 * 100%)}}@media screen and (min-width:768px){.eps-grid-item-md-1{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(1 / 12 * 100%);-ms-flex-preferred-size:calc(1 / 12 * 100%);flex-basis:calc(1 / 12 * 100%)}.eps-grid-item-md-2{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(2 / 12 * 100%);-ms-flex-preferred-size:calc(2 / 12 * 100%);flex-basis:calc(2 / 12 * 100%)}.eps-grid-item-md-3{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(3 / 12 * 100%);-ms-flex-preferred-size:calc(3 / 12 * 100%);flex-basis:calc(3 / 12 * 100%)}.eps-grid-item-md-4{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(4 / 12 * 100%);-ms-flex-preferred-size:calc(4 / 12 * 100%);flex-basis:calc(4 / 12 * 100%)}.eps-grid-item-md-5{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(5 / 12 * 100%);-ms-flex-preferred-size:calc(5 / 12 * 100%);flex-basis:calc(5 / 12 * 100%)}.eps-grid-item-md-6{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(6 / 12 * 100%);-ms-flex-preferred-size:calc(6 / 12 * 100%);flex-basis:calc(6 / 12 * 100%)}.eps-grid-item-md-7{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(7 / 12 * 100%);-ms-flex-preferred-size:calc(7 / 12 * 100%);flex-basis:calc(7 / 12 * 100%)}.eps-grid-item-md-8{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(8 / 12 * 100%);-ms-flex-preferred-size:calc(8 / 12 * 100%);flex-basis:calc(8 / 12 * 100%)}.eps-grid-item-md-9{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(9 / 12 * 100%);-ms-flex-preferred-size:calc(9 / 12 * 100%);flex-basis:calc(9 / 12 * 100%)}.eps-grid-item-md-10{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(10 / 12 * 100%);-ms-flex-preferred-size:calc(10 / 12 * 100%);flex-basis:calc(10 / 12 * 100%)}.eps-grid-item-md-11{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(11 / 12 * 100%);-ms-flex-preferred-size:calc(11 / 12 * 100%);flex-basis:calc(11 / 12 * 100%)}.eps-grid-item-md-12{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(12 / 12 * 100%);-ms-flex-preferred-size:calc(12 / 12 * 100%);flex-basis:calc(12 / 12 * 100%)}}@media screen and (min-width:1025px){.eps-grid-item-lg-1{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(1 / 12 * 100%);-ms-flex-preferred-size:calc(1 / 12 * 100%);flex-basis:calc(1 / 12 * 100%)}.eps-grid-item-lg-2{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(2 / 12 * 100%);-ms-flex-preferred-size:calc(2 / 12 * 100%);flex-basis:calc(2 / 12 * 100%)}.eps-grid-item-lg-3{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(3 / 12 * 100%);-ms-flex-preferred-size:calc(3 / 12 * 100%);flex-basis:calc(3 / 12 * 100%)}.eps-grid-item-lg-4{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(4 / 12 * 100%);-ms-flex-preferred-size:calc(4 / 12 * 100%);flex-basis:calc(4 / 12 * 100%)}.eps-grid-item-lg-5{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(5 / 12 * 100%);-ms-flex-preferred-size:calc(5 / 12 * 100%);flex-basis:calc(5 / 12 * 100%)}.eps-grid-item-lg-6{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(6 / 12 * 100%);-ms-flex-preferred-size:calc(6 / 12 * 100%);flex-basis:calc(6 / 12 * 100%)}.eps-grid-item-lg-7{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(7 / 12 * 100%);-ms-flex-preferred-size:calc(7 / 12 * 100%);flex-basis:calc(7 / 12 * 100%)}.eps-grid-item-lg-8{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(8 / 12 * 100%);-ms-flex-preferred-size:calc(8 / 12 * 100%);flex-basis:calc(8 / 12 * 100%)}.eps-grid-item-lg-9{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(9 / 12 * 100%);-ms-flex-preferred-size:calc(9 / 12 * 100%);flex-basis:calc(9 / 12 * 100%)}.eps-grid-item-lg-10{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(10 / 12 * 100%);-ms-flex-preferred-size:calc(10 / 12 * 100%);flex-basis:calc(10 / 12 * 100%)}.eps-grid-item-lg-11{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(11 / 12 * 100%);-ms-flex-preferred-size:calc(11 / 12 * 100%);flex-basis:calc(11 / 12 * 100%)}.eps-grid-item-lg-12{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(12 / 12 * 100%);-ms-flex-preferred-size:calc(12 / 12 * 100%);flex-basis:calc(12 / 12 * 100%)}}@media screen and (min-width:1440px){.eps-grid-item-xl-1{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(1 / 12 * 100%);-ms-flex-preferred-size:calc(1 / 12 * 100%);flex-basis:calc(1 / 12 * 100%)}.eps-grid-item-xl-2{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(2 / 12 * 100%);-ms-flex-preferred-size:calc(2 / 12 * 100%);flex-basis:calc(2 / 12 * 100%)}.eps-grid-item-xl-3{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(3 / 12 * 100%);-ms-flex-preferred-size:calc(3 / 12 * 100%);flex-basis:calc(3 / 12 * 100%)}.eps-grid-item-xl-4{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(4 / 12 * 100%);-ms-flex-preferred-size:calc(4 / 12 * 100%);flex-basis:calc(4 / 12 * 100%)}.eps-grid-item-xl-5{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(5 / 12 * 100%);-ms-flex-preferred-size:calc(5 / 12 * 100%);flex-basis:calc(5 / 12 * 100%)}.eps-grid-item-xl-6{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(6 / 12 * 100%);-ms-flex-preferred-size:calc(6 / 12 * 100%);flex-basis:calc(6 / 12 * 100%)}.eps-grid-item-xl-7{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(7 / 12 * 100%);-ms-flex-preferred-size:calc(7 / 12 * 100%);flex-basis:calc(7 / 12 * 100%)}.eps-grid-item-xl-8{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(8 / 12 * 100%);-ms-flex-preferred-size:calc(8 / 12 * 100%);flex-basis:calc(8 / 12 * 100%)}.eps-grid-item-xl-9{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(9 / 12 * 100%);-ms-flex-preferred-size:calc(9 / 12 * 100%);flex-basis:calc(9 / 12 * 100%)}.eps-grid-item-xl-10{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(10 / 12 * 100%);-ms-flex-preferred-size:calc(10 / 12 * 100%);flex-basis:calc(10 / 12 * 100%)}.eps-grid-item-xl-11{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(11 / 12 * 100%);-ms-flex-preferred-size:calc(11 / 12 * 100%);flex-basis:calc(11 / 12 * 100%)}.eps-grid-item-xl-12{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(12 / 12 * 100%);-ms-flex-preferred-size:calc(12 / 12 * 100%);flex-basis:calc(12 / 12 * 100%)}}@media screen and (min-width:1600px){.eps-grid-item-xxl-1{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(1 / 12 * 100%);-ms-flex-preferred-size:calc(1 / 12 * 100%);flex-basis:calc(1 / 12 * 100%)}.eps-grid-item-xxl-2{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(2 / 12 * 100%);-ms-flex-preferred-size:calc(2 / 12 * 100%);flex-basis:calc(2 / 12 * 100%)}.eps-grid-item-xxl-3{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(3 / 12 * 100%);-ms-flex-preferred-size:calc(3 / 12 * 100%);flex-basis:calc(3 / 12 * 100%)}.eps-grid-item-xxl-4{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(4 / 12 * 100%);-ms-flex-preferred-size:calc(4 / 12 * 100%);flex-basis:calc(4 / 12 * 100%)}.eps-grid-item-xxl-5{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(5 / 12 * 100%);-ms-flex-preferred-size:calc(5 / 12 * 100%);flex-basis:calc(5 / 12 * 100%)}.eps-grid-item-xxl-6{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(6 / 12 * 100%);-ms-flex-preferred-size:calc(6 / 12 * 100%);flex-basis:calc(6 / 12 * 100%)}.eps-grid-item-xxl-7{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(7 / 12 * 100%);-ms-flex-preferred-size:calc(7 / 12 * 100%);flex-basis:calc(7 / 12 * 100%)}.eps-grid-item-xxl-8{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(8 / 12 * 100%);-ms-flex-preferred-size:calc(8 / 12 * 100%);flex-basis:calc(8 / 12 * 100%)}.eps-grid-item-xxl-9{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(9 / 12 * 100%);-ms-flex-preferred-size:calc(9 / 12 * 100%);flex-basis:calc(9 / 12 * 100%)}.eps-grid-item-xxl-10{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(10 / 12 * 100%);-ms-flex-preferred-size:calc(10 / 12 * 100%);flex-basis:calc(10 / 12 * 100%)}.eps-grid-item-xxl-11{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(11 / 12 * 100%);-ms-flex-preferred-size:calc(11 / 12 * 100%);flex-basis:calc(11 / 12 * 100%)}.eps-grid-item-xxl-12{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;max-width:calc(12 / 12 * 100%);-ms-flex-preferred-size:calc(12 / 12 * 100%);flex-basis:calc(12 / 12 * 100%)}}.eps-theme-dark,:root{--menu-title-color:#6d7882}.eps-menu ul{list-style:none;padding:0;margin:0}.eps-menu ul li{display:-webkit-box;display:-ms-flexbox;display:flex}.eps-menu__title{padding:.5rem 1.875rem;font-size:.6875rem;line-height:1.2;text-transform:uppercase;font-weight:400;color:var(--menu-title-color)}:root{--eps-modal-background-color:#fff;--eps-modal-header-background-color:#58d0f5}.eps-theme-dark{--eps-modal-background-color:#34383c;--eps-modal-header-background-color:#58d0f5}.eps-modal{max-width:43.75rem;background:var(--eps-modal-background-color);border-radius:.1875rem;-webkit-animation:eps-animation-pop .15s cubic-bezier(.57,.53,.71,1.47) forwards;animation:eps-animation-pop .15s cubic-bezier(.57,.53,.71,1.47) forwards}.eps-modal__overlay{background:rgba(0,0,0,.5);position:fixed;display:-webkit-box;display:-ms-flexbox;display:flex;top:0;left:0;width:100%;height:100%;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;z-index:1030}.eps-modal__header{font-size:.875rem;background:var(--eps-modal-header-background-color);height:2.75rem;padding:.625rem 1rem;border-radius:.1875rem}.eps-modal__header,.eps-modal__header .title{color:#fff}.eps-modal__icon{-webkit-margin-end:.625rem;margin-inline-end:.625rem}.eps-modal__body{padding:1.875rem}.eps-modal .eps-tip,.eps-modal__tip{-webkit-padding-start:.75rem;padding-inline-start:.75rem;-webkit-border-start:3px solid #58d0f5;border-inline-start:3px solid #58d0f5}.eps-modal .eps-tip:not(:last-child),.eps-modal__tip:not(:last-child){margin-bottom:1.875rem}.eps-modal .eps-tip:not(:first-child),.eps-modal__section:not(:first-child),.eps-modal__tip:not(:first-child){margin-top:1.875rem}.eps-modal__close-wrapper{-webkit-padding-start:1rem;padding-inline-start:1rem;-webkit-border-start:solid 1px #fff;border-inline-start:solid 1px #fff}.eps-add-new-button{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;--eps-add-new-button-size:1.5rem;line-height:var(--eps-add-new-button-size);cursor:pointer}.eps-add-new-button .eps-icon{background-color:#58d0f5;color:#fff;width:var(--eps-add-new-button-size);height:var(--eps-add-new-button-size);border-radius:100%;font-size:calc(var(--eps-add-new-button-size) * .75);text-align:center;line-height:var(--eps-add-new-button-size)}.eps-add-new-button span:not(.sr-only){-webkit-margin-start:.625rem;margin-inline-start:.625rem;font-weight:500}.eps-add-new-button--sm{--eps-add-new-button-size:1rem}:root{--select2-selection-background-color:#fff;--select2-selection-color:#6d7882;--select2-selection-border-color:#d5dadf;--select2-selection-opened-focused-border-color:#f1f3f5;--select2-single-selection-rendered-color:#6d7882;--select2-default-single-selection-background-color:#fff;--select2-default-single-selection-border-color:#d5dadf;--select2-default-multiple-selection-background-color:#fff;--select2-default-multiple-selection-choice-background-color:#f1f3f5;--select2-default-multiple-selection-choice-color:#6d7882;--select2-default-multiple-selection-choice-border-color:#f1f3f5;--select2-default-multiple-selection-choice-remove-color:#a4afb7;--select2-default-multiple-selection-choice-remove-hover-color:#6d7882;--select2-default-results-selected-option-background-color:#fff;--select2-default-results-selected-option-color:#6d7882;--select2-default-results-highlighted-option-background-color:#5897fb;--select2-default-results-highlighted-option-color:#fff;--select2-results-selected-option-background-color:#5897fb;--select2-results-selected-option-color:#fff;--select2-dropdown-background-color:#fff;--select2-dropdown-border-color:#d5dadf}.eps-theme-dark{--select2-selection-background-color:#34383c;--select2-selection-color:#e0e1e3;--select2-selection-border-color:#64666a;--select2-selection-opened-focused-border-color:#7d7e82;--select2-single-selection-rendered-color:#e0e1e3;--select2-default-single-selection-background-color:#34383c;--select2-default-single-selection-border-color:#4c4f56;--select2-default-multiple-selection-background-color:#34383c;--select2-default-multiple-selection-choice-background-color:#4c4f56;--select2-default-multiple-selection-choice-color:#e0e1e3;--select2-default-multiple-selection-choice-border-color:#4c4f56;--select2-default-multiple-selection-choice-remove-color:#b4b5b7;--select2-default-multiple-selection-choice-remove-hover-color:#e0e1e3;--select2-default-results-selected-option-background-color:#34383c;--select2-default-results-selected-option-color:#e0e1e3;--select2-default-results-highlighted-option-background-color:#4c4f56;--select2-default-results-highlighted-option-color:#e0e1e3;--select2-results-selected-option-background-color:#4c4f56;--select2-results-selected-option-color:#e0e1e3;--select2-dropdown-background-color:#34383c;--select2-dropdown-border-color:#64666a}.select2-container:not(.select2-container--open):not(.select2-container--focus) .select2-selection--multiple,.select2-container:not(.select2-container--open):not(.select2-container--focus) .select2-selection--single{background-color:var(--select2-selection-background-color);color:var(--select2-selection-color);border-color:var(--select2-selection-border-color)}.select2-container.select2-container--focus .select2-selection--multiple,.select2-container.select2-container--focus .select2-selection--single,.select2-container.select2-container--open .select2-selection--multiple,.select2-container.select2-container--open .select2-selection--single{border-color:var(--select2-selection-opened-focused-border-color)}.select2-container.select2-container--default .select2-selection--single .select2-selection__rendered{color:var(--select2-single-selection-rendered-color)}.select2-container--default .select2-selection--single{background-color:var(--select2-default-single-selection-background-color);border-color:var(--select2-default-single-selection-border-color)}.select2-container--default .select2-selection--multiple{background-color:var(--select2-default-multiple-selection-background-color)}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:var(--select2-default-multiple-selection-choice-background-color);color:var(--select2-default-multiple-selection-choice-color);border-color:var(--select2-default-multiple-selection-choice-border-color)}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:var(--select2-default-multiple-selection-choice-remove-color)}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:var(--select2-default-multiple-selection-choice-remove-hover-color)}.select2-container--default .select2-results__option[aria-selected]{background-color:var(--select2-default-results-selected-option-background-color);color:var(--select2-default-results-selected-option-color)}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:var(--select2-default-results-highlighted-option-background-color);color:var(--select2-default-results-highlighted-option-color)}.select2-container .select2-results__option[aria-selected=true]{background-color:var(--select2-results-selected-option-background-color);color:var(--select2-results-selected-option-color)}.select2-container .select2-dropdown{background-color:var(--select2-dropdown-background-color);border-color:var(--select2-dropdown-border-color)}.eps-notice{--eps-box-notice-color:#a4afb7;--eps-box-notice-background-color:#fcfcfc;padding:.625rem 1rem;-webkit-box-shadow:0 2px 3px 1px var(--color-box-shadow-color);box-shadow:0 2px 3px 1px var(--color-box-shadow-color);background-color:var(--eps-box-notice-background-color)}.eps-notice-semantic{-webkit-border-start:3px solid var(--eps-notice-semantic-color);border-inline-start:3px solid var(--eps-notice-semantic-color)}.eps-notice-semantic .eps-notice__icon{color:var(--eps-notice-semantic-color);font-size:1rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem}.eps-notice--warning{--eps-notice-semantic-color:#fcb92c}.eps-notice--danger{--eps-notice-semantic-color:#b01b1b}.eps-notice--info{--eps-notice-semantic-color:#58d0f5}.eps-notice__text{margin:0;padding:0;color:var(--eps-box-notice-color);font-style:italic}.eps-notice__button-container{-ms-flex-negative:0;flex-shrink:0;margin-left:1.25rem;width:auto}.eps-theme-dark .eps-notice{--eps-box-notice-color:#b4b5b7;--eps-box-notice-background-color:#404349}.eps-list{--eps-list-item-separated-border-color:#f1f3f5;padding:0;margin:0;list-style-type:none}.eps-list--padding{padding:var(--eps-list-padding)}.eps-list__item{padding:0}.eps-list__item--padding{padding:var(--eps-list-item-padding)}.eps-list--separated .eps-list__item:not(:last-child){border-bottom:1px solid var(--eps-list-item-separated-border-color)}.eps-theme-dark .eps-list{--eps-list-item-separated-border-color:#34383c}:root{--popover-background-color:#fff;--popover-item-color:#6d7882;--popover-item-hover-color:#556068;--popover-item-danger-hover-color:#b01b1b;--popover-item-background-color:#fff;--popover-box-shadow-color:rgba(0,0,0,0.15);--popover-box-shadow-size:0px 1px 20px;--popover-arrow-color:#fff}.eps-theme-dark{--popover-background-color:#4c4f56;--popover-item-color:#fff;--popover-item-hover-color:#e0e1e3;--popover-item-danger-hover-color:#f84343;--popover-item-background-color:#4c4f56;--popover-box-shadow-color:rgba(0,0,0,0.15);--popover-box-shadow-size:0px 1px 20px;--popover-arrow-color:#4c4f56}.eps-popover{padding:10px 0;background-color:var(--popover-background-color);-webkit-box-shadow:var(--popover-box-shadow-size) var(--popover-box-shadow-color);box-shadow:var(--popover-box-shadow-size) var(--popover-box-shadow-color);list-style:none;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;min-width:120px;border-radius:.1875rem;position:absolute;z-index:1050;margin-top:9px;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);left:.25rem}.eps-popover__background{position:fixed;top:0;bottom:0;left:0;right:0;z-index:1030}.eps-popover__container{position:relative}.eps-popover:before{content:"";display:block;position:absolute;width:16px;height:9px;margin:0 .1875rem 9px;top:-9px;left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);border-left:calc(16px / 2) solid transparent;border-right:calc(16px / 2) solid transparent;border-top:0 solid transparent;border-bottom:9px solid transparent;border-bottom-color:var(--popover-arrow-color)}.eps-popover__item{padding:.3125rem 1rem;background-color:var(--popover-item-background-color);color:var(--popover-item-color);font-size:.6875rem;font-weight:500;line-height:.8125rem;width:100%;-webkit-box-align:center;-ms-flex-align:center;align-items:center;cursor:pointer}.eps-popover__item:hover{color:var(--popover-item-hover-color)}.eps-popover__item--danger:hover{color:var(--popover-item-danger-hover-color)}.eps-popover__item .eps-icon{font-size:inherit;-webkit-margin-end:.3125rem;margin-inline-end:.3125rem}.eps-css-grid{display:grid;grid-template-columns:repeat(var(--eps-grid-columns,auto-fill),minmax(var(--eps-grid-col-min-width,1fr),var(--eps-grid-col-max-width,1fr)));grid-gap:var(--eps-grid-spacing)}.eps-box{--eps-box-background-color:#fff;--eps-box-color:#495157;--eps-box-input-color:#495157;padding:0;border-radius:.1875rem;background-color:var(--eps-box-background-color);color:var(--eps-box-color)}.eps-box--padding{padding:var(--eps-box-padding)}.eps-box>input{width:100%;outline:0;border:0;background-color:var(--eps-box-background-color);color:var(--eps-box-input-color)}.eps-theme-dark .eps-box{--eps-box-background-color:#404349;--eps-box-color:#e0e1e3;--eps-box-input-color:#e0e1e3}:root{--checkbox-border-color:#d5dadf;--checkbox-hover-border-color:#c7cdd4;--checkbox-active-border-color:#e3e7ea;--checkbox-background-color:#fff;--checkbox-checked-background-color:#39b54a;--checkbox-checked-hover-background-color:#33a242;--checkbox-checked-active-background-color:#44c455;--checkbox-checked-disabled-background-color:#c2cbd2;--checkbox-indicator-color:#fff;--checkbox-error-background-color:#b01b1b}.eps-theme-dark{--checkbox-background-color:transparent}.eps-checkbox{-webkit-appearance:none;border-radius:.1875rem;width:15px;height:15px;outline:0;background-color:var(--checkbox-background-color);display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid var(--checkbox-border-color)}.eps-checkbox:after{display:inline-block;margin-bottom:calc(.25 / 2 * 100%);content:" ";width:3px;height:6px;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.eps-checkbox:hover{--checkbox-border-color:var(--checkbox-hover-border-color)}.eps-checkbox:active{--checkbox-border-color:var(--checkbox-active-border-color)}.eps-checkbox:checked{--checkbox-background-color:var(--checkbox-checked-background-color);--checkbox-border-color:var(--checkbox-checked-background-color)}.eps-checkbox:checked:after{border:solid #fff;border-width:0 1px 1px 0}.eps-checkbox:checked:hover{--checkbox-background-color:var(--checkbox-checked-hover-background-color);--checkbox-border-color:var(--checkbox-checked-hover-background-color)}.eps-checkbox:checked:active{--checkbox-background-color:var(--checkbox-checked-active-background-color);--checkbox-border-color:var(--checkbox-checked-active-background-color)}.eps-checkbox:checked:disabled{--checkbox-background-color:var(--checkbox-checked-disabled-background-color);--checkbox-border-color:var(--checkbox-checked-disabled-background-color)}.eps-checkbox--rounded{border-radius:50%}.eps-checkbox--indeterminate{--checkbox-background-color:var(--checkbox-checked-background-color);--checkbox-border-color:var(--checkbox-checked-background-color)}.eps-checkbox--indeterminate:after{display:inline-block;margin-bottom:0;content:" ";width:7px;height:0;-webkit-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg);border-top:1px solid #fff}.eps-checkbox--error:after,.eps-checkbox--error:before,.eps-checkbox--error:checked:after,.eps-checkbox--error:checked:before{display:inline-block;margin-bottom:0;content:" ";width:7px;height:0;border:solid #fff;border-width:1px 0 0;position:absolute}.eps-checkbox--error:before,.eps-checkbox--error:checked:before{-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.eps-checkbox--error:after,.eps-checkbox--error:checked:after{-webkit-transform:rotate(-45deg);-ms-transform:rotate(-45deg);transform:rotate(-45deg)}.eps-checkbox--error,.eps-checkbox--error:checked,.eps-checkbox--error:checked:hover,.eps-checkbox--error:hover{--checkbox-background-color:var(--checkbox-error-background-color);--checkbox-border-color:var(--checkbox-error-background-color)}:root{--e-app-drag-drop-background-color:#fff;--e-app-drag-drop-outline-color:#d5dadf}.eps-theme-dark{--e-app-drag-drop-background-color:#404349;--e-app-drag-drop-outline-color:#7d7e82}.e-app-drag-drop{background-color:var(--e-app-drag-drop-background-color);outline:2px dashed var(--e-app-drag-drop-outline-color);outline-offset:-.75rem;margin-bottom:1.5rem;padding:4.125rem;text-align:center}.e-app-drag-drop--drag-over{outline-color:#58d0f5}.eps-dialog{border-radius:3px;width:375px}.eps-dialog__close-button{position:absolute;top:-2.75rem;right:-2.75rem;margin-top:.625rem;margin-right:.625rem;z-index:1040;font-size:1.25rem;color:#fff}.eps-dialog__content{padding:1.5rem 1.875rem 1rem;font-size:.75rem}.eps-dialog__text,.eps-dialog__title{text-align:center}.eps-dialog__buttons{display:-webkit-box;display:-ms-flexbox;display:flex}.eps-dialog__button{-webkit-box-flex:1;-ms-flex:1;flex:1;border-top:1px solid var(--hr-color);line-height:2.75rem;text-align:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.eps-dialog__button:last-child:not(:first-child){-webkit-border-start:1px solid var(--hr-color);border-inline-start:1px solid var(--hr-color)}.e-app__popover{display:none;position:absolute;-webkit-box-shadow:0 2px 15px rgba(0,0,0,.3);box-shadow:0 2px 15px rgba(0,0,0,.3);border-radius:6px;padding:20px;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;z-index:999;background-color:#fff}.e-app__popover:before{content:"";position:absolute;top:-16px;left:var(--popover-arrow-offset-end,22px);border:8px solid transparent;border-bottom-color:#fff}.eps-inline-link{color:var(--eps-inline-link-color);background-color:initial;border:0;padding:0}.eps-inline-link--color-primary{--eps-inline-link-color:#39b54a}.eps-inline-link--color-secondary{--eps-inline-link-color:#c2cbd2}.eps-inline-link--color-danger{--eps-inline-link-color:#b01b1b}.eps-inline-link--color-cta{--eps-inline-link-color:#93003f}.eps-inline-link--color-link{--eps-inline-link-color:#58d0f5}.eps-inline-link--color-disabled{--eps-inline-link-color:#c2cbd2}.eps-inline-link--underline-always,.eps-inline-link--underline-always:hover,.eps-inline-link--underline-hover:hover{text-decoration:underline}.eps-inline-link--italic{font-style:italic}.eps-inline-link,.eps-inline-link:focus{outline:none}.eps-text-field{--eps-text-field-color:#6d7882;--eps-text-field-background-color:#fff;--eps-text-field-placeholder-color:#a4afb7;--eps-text-field-outlined-border-color:#d5dadf;--eps-text-field-outlined-focus-border-color:#c2cbd2;width:100%;color:var(--eps-text-field-color);background-color:var(--eps-text-field-background-color);border:0;outline:none;font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-size:.875rem;font-weight:400;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.eps-text-field--outlined{border-radius:.1875rem;border:1px solid var(--eps-text-field-outlined-border-color);padding:.625rem}.eps-text-field--outlined:focus{border-color:var(--eps-text-field-outlined-focus-border-color)}.eps-text-field::-webkit-input-placeholder{color:var(--eps-text-field-placeholder-color);font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-size:.875rem;font-weight:400;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.eps-text-field::-moz-placeholder{color:var(--eps-text-field-placeholder-color);font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-size:.875rem;font-weight:400;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.eps-text-field::-ms-input-placeholder{color:var(--eps-text-field-placeholder-color);font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-size:.875rem;font-weight:400;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.eps-text-field::placeholder{color:var(--eps-text-field-placeholder-color);font-family:Roboto,Arial,Helvetica,Verdana,sans-serif;font-size:.875rem;font-weight:400;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.eps-theme-dark .eps-text-field{--eps-text-field-color:#b4b5b7;--eps-text-field-background-color:#34383c;--eps-text-field-placeholder-color:#7d7e82;--eps-text-field-outlined-border-color:#64666a;--eps-text-field-outlined-focus-border-color:#7d7e82}.e-app-import-export-content-layout{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;height:100%}.e-app-import-export-content-layout__container{-ms-flex-preferred-size:1075px;flex-basis:1075px}.e-app-export-complete__kit-content-title{margin:2.75rem 0 .625rem}.e-app-export-kit-content{--e-app-export-kit-content-title-color:#556068;--e-app-export-kit-content-description-color:#6d7882}.e-app-export-kit-content__checkbox{-ms-flex-negative:0;flex-shrink:0;-webkit-margin-end:.75rem;margin-inline-end:.75rem}.e-app-export-kit-content__title{color:var(--e-app-export-kit-content-title-color)}.e-app-export-kit-content__description{color:var(--e-app-export-kit-content-description-color);-webkit-margin-end:1.25rem;margin-inline-end:1.25rem}.e-app-export-kit-content__notice{margin-top:1rem}.e-app-export-kit-content__small-notice{font-style:italic;color:var(--e-app-export-kit-content-description-color)}.eps-theme-dark .e-app-export-kit-content{--e-app-export-kit-content-title-color:#e0e1e3;--e-app-export-kit-content-description-color:#b4b5b7}.e-app-import-export-kit-data{--e-app-import-export-kit-data-site-area-color:#556068;--e-app-import-export-kit-data-included-color:#a4afb7}.e-app-import-export-kit-data__included,.e-app-import-export-kit-data__site-area{margin-bottom:0}.e-app-import-export-kit-data__site-area{color:var(--e-app-import-export-kit-data-site-area-color);font-weight:700}.e-app-import-export-kit-data__included{color:var(--e-app-import-export-kit-data-included-color)}.eps-theme-dark .e-app-import-export-kit-data{--e-app-import-export-kit-data-site-area-color:#b4b5b7;--e-app-import-export-kit-data-included-color:#7d7e82}.e-app-import-resolver{--e-app-import-resolver-panel-header-background-color:#fff;--e-app-import-resolver-panel-body-background-color:hsla(0,0%,100%,0.5);--e-app-import-resolver-conflicts-asset-border-color:#c2cbd2;--e-app-import-resolver-conflicts-asset-inactive-color:#a4afb7;padding-bottom:1.25rem}.e-app-import-resolver__notice{margin-bottom:1.25rem}.e-app-import-resolver__panel,.e-app-import-resolver__panel:hover{background-color:initial}.e-app-import-resolver__panel-header{background-color:var(--e-app-import-resolver-panel-header-background-color)}.e-app-import-resolver__panel-body{background-color:var(--e-app-import-resolver-panel-body-background-color)}.e-app-import-resolver-conflicts__container{-webkit-box-shadow:0 2px 3px 1px var(--color-box-shadow-color);box-shadow:0 2px 3px 1px var(--color-box-shadow-color)}.e-app-import-resolver-conflicts__checkbox{-ms-flex-negative:0;flex-shrink:0;-webkit-margin-end:.75rem;margin-inline-end:.75rem}.e-app-import-resolver-conflicts__title{line-height:1}.e-app-import-resolver-conflicts__asset:not(:first-child){-webkit-border-start:2px solid var(--e-app-import-resolver-conflicts-asset-border-color);border-inline-start:2px solid var(--e-app-import-resolver-conflicts-asset-border-color);-webkit-padding-start:1rem;padding-inline-start:1rem;-webkit-margin-start:1rem;margin-inline-start:1rem}.e-app-import-resolver-conflicts__asset:not(.active){color:var(--e-app-import-resolver-conflicts-asset-inactive-color)}.e-app-import-resolver-conflicts__edit-template{-webkit-margin-start:.3125rem;margin-inline-start:.3125rem}.eps-theme-dark .e-app-import-resolver{--e-app-import-resolver-panel-header-background-color:#4c4f56;--e-app-import-resolver-panel-body-background-color:rgba(0,0,0,0.05);--e-app-import-resolver-conflicts-asset-border-color:#64666a;--e-app-import-resolver-conflicts-asset-inactive-color:#7d7e82}.eps-panel{--eps-panel-header-background-color:#fff;--eps-panel-body-background-color:hsla(0,0%,100%,0.5)}.eps-panel,.eps-panel:hover{background-color:initial}.eps-panel__header{background-color:var(--eps-panel-header-background-color);border-radius:.1875rem}.eps-panel__body{background-color:var(--eps-panel-body-background-color);border-radius:0 0 .1875rem .1875rem}.eps-theme-dark .eps-panel{--eps-panel-header-background-color:#404349;--eps-panel-body-background-color:rgba(64,67,73,0.5)}.e-app-export-kit{padding-bottom:1.25rem}.e-app-export-kit-information{margin-top:1.25rem}.e-app-export-kit-information__field-header{margin-bottom:.625rem}.e-app-export-kit-information__label{margin:0}.e-app-export-kit-information__info-icon{-webkit-margin-start:.625rem;margin-inline-start:.625rem}.e-app-export-kit-info-modal__icon{-webkit-padding-start:.625rem;padding-inline-start:.625rem}.e-app-export-kit-info-modal__heading{margin-bottom:1.25rem}.e-app-import-export-info-modal__section:not(:first-child){margin-top:1.875rem}.e-app-import-export-info-modal__heading{margin-bottom:1.25rem}:root{--eps-badge-background-color:#fff}.eps-theme-dark{--eps-badge-background-color:#404349}.eps-badge{display:inline-block;background:var(--eps-badge-background-color);padding:0 .5rem;line-height:1.8;-webkit-box-shadow:0 3px 6px var(--color-box-shadow-color);box-shadow:0 3px 6px var(--color-box-shadow-color);border-radius:4px;font-size:.75rem}.eps-badge--sm{font-size:.625rem;border-radius:3px;padding:0 .3125rem;line-height:1.5}.eps-collapse__title{cursor:pointer;padding:.3125rem 0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;width:100%;background:transparent;border:none;color:inherit}.eps-collapse__title:focus{outline:none}.eps-collapse__icon{-webkit-transition:all .15s;-o-transition:all .15s;transition:all .15s;-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.eps-collapse__content{margin-top:.625rem;display:none}.eps-collapse[data-open] .eps-collapse__content{display:block}.eps-collapse[data-open] .eps-collapse__icon{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.e-kit-library-bottom-promotion{--e-kit-library-bottom-promotion-color:tints(600)}.eps-theme-dark .e-kit-library-bottom-promotion{--e-kit-library-bottom-promotion-color:dark-tints(400)}.e-kit-library-bottom-promotion{width:100%;text-align:center;margin-top:1.875rem;color:var(--e-kit-library-bottom-promotion-color)}.e-kit-library__error-screen{margin-top:2.75rem}.e-kit-library__error-screen-title{margin-top:2.75rem;margin-bottom:0}.e-kit-library__error-screen-description{margin-top:1.5rem;color:#a4afb7;text-align:center;max-width:520px}.e-kit-library__kit-favorite-actions{padding:.3125rem;-webkit-transition:all .3s;-o-transition:.3s all;transition:all .3s;border-radius:4px}.e-kit-library__kit-favorite-actions--active{color:#b01b1b}.e-kit-library__kit-favorite-actions--loading{opacity:1%;cursor:default}.e-kit-library__kit-favorite-actions:hover{background-color:rgba(176,27,27,.1)}.e-kit-library__filter-indication{margin-top:1.5rem}.e-kit-library__filter-indication,.e-kit-library__filter-indication-text{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.e-kit-library__filter-indication-text{margin-bottom:0}.e-kit-library__filter-indication-badge{margin-right:.3125rem;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.e-kit-library__filter-indication-badge-remove{margin-right:.3125rem;font-size:.875rem}.e-kit-library__filter-indication-button{margin-right:1.5rem}#eps-app-header-btn-apply,#eps-app-header-btn-connect,#eps-app-header-btn-promotion{margin-right:.625rem;margin-left:.625rem}.e-kit-library__apply-button{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:.3125rem}.e-kit-library__kit-item{--e-kit-library-kit-item-overlay-promotion-button-background-color:#fcfcfc}.eps-theme-dark .e-kit-library__kit-item{--e-kit-library-kit-item-overlay-promotion-button-background-color:#404349}.e-kit-library__kit-item-overlay{height:100%}.e-kit-library__kit-item-overlay>:first-child{-webkit-box-flex:1;-ms-flex:1;flex:1}.e-kit-library__kit-item-overlay-overview-button{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;color:#fff;height:100%;width:100%}.e-kit-library__kit-item-overlay-overview-button>i{font-size:2rem;margin-bottom:5px}.e-kit-library__kit-item-overlay-overview-button>span{font-size:.9rem}.e-kit-library__kit-item-overlay-promotion-button{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%;background:#fff;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding:10px;font-size:13px;color:#93003f;background:var(--e-kit-library-kit-item-overlay-promotion-button-background-color)}.e-kit-library__kit-item-overlay-promotion-button>*{margin:0 3px}.e-kit-library__kit-item-subscription-plan-badge{position:absolute;top:0;right:0;margin:.3125rem;color:#fff;text-transform:uppercase}:root{--e-kit-library-header-back-border:1px solid #f1f3f5;--e-kit-library-header-back-color:#a4afb7}.eps-theme-dark{--e-kit-library-header-back-border:1px solid #64666a;--e-kit-library-header-back-color:#b4b5b7}.e-kit-library__header-back{color:var(--e-kit-library-header-back-color);padding-left:1.25rem;padding-right:.3125rem;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:100%;border-left:var(--e-kit-library-header-back-border)}.e-kit-library__header-back-container{-webkit-box-flex:1;-ms-flex:1;flex:1;height:100%}.e-kit-library__header-back .eps-icon{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.e-kit-library__page-loader{width:100%;height:100%;display:grid;place-items:center;font-size:1.85rem;color:#a4afb7}.eps-search-input{--eps-search-input-background-color:#fcfcfc;--eps-search-input-background-color-focus:#fff;--eps-search-input-color:#556068;--eps-search-input-placeholder-color:#a4afb7}.eps-theme-dark .eps-search-input{--eps-search-input-background-color:#404349;--eps-search-input-background-color-focus:#4c4f56;--eps-search-input-color:#b4b5b7;--eps-search-input-placeholder-color:#b4b5b7}.eps-search-input{width:100%;font-size:.9375rem;padding:.625rem 2.75rem;border:none;background:var(--eps-search-input-background-color);outline:none;color:var(--eps-search-input-color);line-height:1;height:2.75rem}.eps-search-input--sm{font-size:.8125rem;padding:.5rem 1.875rem}.eps-search-input:focus{background:var(--eps-search-input-background-color-focus)}.eps-search-input::-webkit-input-placeholder{color:var(--eps-search-input-placeholder-color);font-style:italic}.eps-search-input::-moz-placeholder{color:var(--eps-search-input-placeholder-color);font-style:italic}.eps-search-input::-ms-input-placeholder{color:var(--eps-search-input-placeholder-color);font-style:italic}.eps-search-input::placeholder{color:var(--eps-search-input-placeholder-color);font-style:italic}.eps-search-input__container{position:relative}.eps-search-input__icon{font-size:1.25rem;padding:.625rem;color:#a4afb7;position:absolute;top:0;inset-inline-start:0;height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.eps-search-input__icon--sm{font-size:.75rem}.eps-search-input__clear-icon{font-size:.875rem;padding:.625rem;color:#a4afb7;position:absolute;top:0;left:0;height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.eps-search-input__clear-icon--sm{font-size:.6875rem}:root{--eps-sort-select-select-background-color:#fcfcfc;--eps-sort-select-select-color:#556068;--eps-sort-select-button-background-color:#fcfcfc;--eps-sort-select-button-border:1px solid #f1f3f5}.eps-theme-dark{--eps-sort-select-select-background-color:#404349;--eps-sort-select-select-color:#b4b5b7;--eps-sort-select-button-background-color:#404349;--eps-sort-select-button-border:1px solid #26292c}.eps-sort-select{width:100%;font-size:.9375rem;display:-webkit-box;display:-ms-flexbox;display:flex}.eps-sort-select__select-wrapper{-webkit-box-flex:1;-ms-flex:1;flex:1;position:relative}.eps-sort-select__select-wrapper:after{content:"\e8ad";font-family:eicons;position:absolute;left:.625rem;top:0;bottom:0;color:#a4afb7;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;pointer-events:none}.eps-sort-select__select{width:100%;padding:.625rem;border:none;background:var(--eps-sort-select-select-background-color);outline:none;color:var(--eps-sort-select-select-color);line-height:1;cursor:pointer;height:2.75rem;-moz-appearance:none;appearance:none;-webkit-appearance:none;border-radius:0}.eps-sort-select__button{padding:.75rem;background:var(--eps-sort-select-button-background-color);border-right:var(--eps-sort-select-button-border);line-height:1;color:#a4afb7}.e-kit-library__tags-filter{--e-kit-library-tags-filter-list-search-background-color:#fff}.eps-theme-dark .e-kit-library__tags-filter{--e-kit-library-tags-filter-list-search-background-color:#404349}.e-kit-library__tags-filter{margin-top:2.75rem}.e-kit-library__tags-filter-list{margin-bottom:2.75rem}.e-kit-library__tags-filter-list .eps-collapse__title{padding-right:1.875rem;padding-left:1.875rem;text-transform:uppercase}.e-kit-library__tags-filter-list .eps-collapse__content{margin:.3125rem 1.875rem}.e-kit-library__tags-filter-list-container{max-height:250px;overflow:auto}.e-kit-library__tags-filter-list-search{margin-bottom:.625rem}.e-kit-library__tags-filter-list-search .eps-search-input{background:var(--e-kit-library-tags-filter-list-search-background-color)}.e-kit-library__tags-filter-list-item{padding:.625rem 0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-weight:500}.e-kit-library__tags-filter-list-item input{margin-left:.3125rem}.e-kit-library #eps-app-header-btn-refetch{padding:0}.e-kit-library-header-info-modal-container{margin-bottom:2.75rem}.e-kit-library__tooltip{padding:5px 12px;color:#fff;background-color:#26292c;font-size:10px}.e-kit-library__tooltip:before{border-bottom-color:#26292c}.e-kit-library__index-layout-container{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;overflow-y:auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.e-kit-library__index-layout-top-area{padding:1.875rem 2.75rem;position:sticky;top:-1px;width:100%;z-index:2;background-color:var(--app-background-color);gap:1.5rem}.e-kit-library__index-layout-top-area-search,.e-kit-library__index-layout-top-area-sort{min-width:265px}.e-kit-library__index-layout-top-area-search{-webkit-box-flex:1;-ms-flex:1;flex:1}.e-kit-library__index-layout-main{padding-top:0;padding-bottom:1.5rem;overflow-y:hidden;height:auto;-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.e-kit-library__tags-filter{--e-kit-library-item-sidebar-header-title-color:#6d7882;--e-kit-library-item-sidebar-description-color:#6d7882;--e-kit-library-item-information-text-color:#6d7882}.eps-theme-dark .e-kit-library__tags-filter{--e-kit-library-item-sidebar-header-title-color:#b4b5b7;--e-kit-library-item-sidebar-description-color:#b4b5b7;--e-kit-library-item-information-text-color:#b4b5b7}.e-kit-library__item-sidebar{padding:1.5rem 1.875rem}.e-kit-library__item-sidebar-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.e-kit-library__item-sidebar-header-title{color:var(--e-kit-library-item-sidebar-header-title-color);margin-bottom:0}.e-kit-library__item-sidebar-thumbnail{margin-top:1.5rem;-webkit-box-shadow:0 4px 10px var(--color-box-shadow-color);box-shadow:0 4px 10px var(--color-box-shadow-color)}.e-kit-library__item-sidebar-description{margin-top:1.5rem;color:var(--e-kit-library-item-sidebar-description-color)}.e-kit-library__item-sidebar-collapse-tags{margin-top:2.75rem}.e-kit-library__item-sidebar-collapse-info{margin-top:1.875rem}.e-kit-library__item-sidebar-tags-container{gap:.625rem}.e-kit-library__item-information-text{font-size:.8125rem;color:var(--e-kit-library-item-information-text-color);margin-bottom:.3125rem}.e-kit-library{--e-kit-library-content-overview-group-title-color:#6d7882}.eps-theme-dark .e-kit-library{--e-kit-library-content-overview-group-title-color:#b4b5b7}.e-kit-library__content-overview-group-item{margin-bottom:2.75rem}.e-kit-library__content-overview-group-title{margin-bottom:1.875rem;color:var(--e-kit-library-content-overview-group-title-color)}.e-kit-library__preview-responsive-controls{width:auto}.e-kit-library__preview-responsive-controls-item{margin:0 .3125rem;color:#a4afb7;padding:.3125rem}.e-kit-library__preview-responsive-controls-item:hover{background:rgba(88,208,245,.1);border-radius:3px;-webkit-transition:all .3s;-o-transition:all .3s;transition:all .3s}.e-kit-library__preview-responsive-controls-item--active{color:#58d0f5}.e-kit-library__preview-loader{position:absolute;top:0;left:0;z-index:0}.e-kit-library__preview-iframe{border:none;-webkit-transition:all .3s;-o-transition:all .3s;transition:all .3s;-webkit-box-shadow:0 4px 10px var(--color-box-shadow-color);box-shadow:0 4px 10px var(--color-box-shadow-color)}.e-kit-library__preview-iframe-container{overflow-y:auto;position:relative;z-index:1}.e-app-collapse{--e-app-collapse-icon-color:#6d7882}.e-app-collapse-toggle{position:relative}.e-app-collapse-toggle--active{cursor:pointer}.e-app-collapse-toggle__icon{color:var(--e-app-collapse-icon-color);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;font-size:.875rem;position:absolute;top:50%;right:var(--e-app-collapse-toggle-icon-spacing);-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.e-app-collapse-toggle__icon:before{-webkit-transition:all .2s linear;-o-transition:all .2s linear;transition:all .2s linear}.e-app-collapse-content{display:none}.e-app-collapse--opened .e-app-collapse-toggle__icon:before{-webkit-transform:rotate(-180deg);-ms-transform:rotate(-180deg);transform:rotate(-180deg)}.e-app-collapse--opened .e-app-collapse-content{display:block}[dir=rtl] .e-app-collapse-toggle__icon{right:auto;left:var(--e-app-collapse-toggle-icon-spacing)}.eps-theme-dark .e-app-collapse{--e-app-collapse-icon-color:#b4b5b7}.e-app-import-plugins{--e-app-import-plugins-selection-section-heading-color:#6d7882;padding-bottom:1.5rem}.e-app-import-plugins__section{margin-top:1.875rem}.e-app-import-plugins__section-heading{color:var(--e-app-import-plugins-selection-section-heading-color);margin-bottom:1rem}.e-app-import-plugins__versions-notice{margin-bottom:.75rem}.eps-theme-dark .e-app-import-plugins{--e-app-import-plugins-selection-section-heading-color:#b4b5b7}.eps-table{--eps-table-body-color:#556068;--eps-table-body-background-color:#fff;border-spacing:0 2px;table-layout:fixed;width:100%}.eps-table__checkboxes-column{width:1.875rem}.eps-table__checkbox{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-negative:0;flex-shrink:0}.eps-table__cell{padding:1rem}.eps-table__head .eps-table__cell{text-align:start}.eps-table__body .eps-table__row{background-color:var(--eps-table-body-background-color)}.eps-table__body .eps-table__cell:first-child{border-radius:.1875rem 0 0 .1875rem}.eps-table__body .eps-table__cell:last-child{border-radius:0 .1875rem .1875rem 0}.eps-table--selection .eps-table__cell:first-child{-webkit-padding-end:0;padding-inline-end:0}.eps-theme-dark .eps-table{--eps-table-body-color:#b4b5b7;--eps-table-body-background-color:#404349}[dir=rtl] .eps-table__body [dir=rtl] .eps-table__cell:first-child{border-radius:0 .1875rem .1875rem 0}[dir=rtl] .eps-table__body [dir=rtl] .eps-table__cell:last-child{border-radius:.1875rem 0 0 .1875rem}.e-app-import-plugins-pro-banner{--e-app-import-plugins-pro-banner-heading-color:#556068;--e-app-import-plugins-pro-banner-description-color:#6d7882;margin-bottom:1.875rem}.e-app-import-plugins-pro-banner__heading{color:var(--e-app-import-plugins-pro-banner-heading-color);margin-bottom:.625rem}.e-app-import-plugins-pro-banner__description{color:var(--e-app-import-plugins-pro-banner-description-color);margin-bottom:0}.eps-theme-dark .e-app-import-plugins-pro-banner{--e-app-import-plugins-pro-banner-heading-color:#7d7e82;--e-app-import-plugins-pro-banner-description-color:#b4b5b7}.e-app-export-plugins,.e-app-import-content{padding-bottom:1.25rem}.e-app-import-content__plugins-notice{margin-bottom:1.25rem}.e-app-import-plugins-activation__installing-plugins{padding:.625rem 0}.e-app-import-plugins-activation__plugin-name{-webkit-margin-start:.5rem;margin-inline-start:.5rem}.e-app-import-plugins-activation__plugin-status-item{margin-bottom:.75rem}.e-app-import-export-plugins-table__cell-content{margin-bottom:0;text-transform:capitalize}.e-app-import-export-loader{--e-app-import-export-loader-color:#c2cbd2;color:var(--e-app-import-export-loader-color);font-size:50px}.e-app-import-export-loader.eicon-loading{font-size:1.85rem}.e-app-import-export-loader--absolute-center{position:absolute;top:50%;left:50%;-webkit-transform:translateX(-50%) translateY(-50%);-ms-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.eps-theme-dark .e-app-import-export-loader{--e-app-import-export-loader-color:#64666a}.e-app-import-export-message-banner{--e-app-import-export-message-banner-heading-color:#556068;--e-app-import-export-message-banner-description-color:#6d7882;margin-bottom:1.875rem}.e-app-import-export-message-banner__heading{color:var(--e-app-import-export-message-banner-heading-color);margin-bottom:.625rem}.e-app-import-export-message-banner__description{color:var(--e-app-import-export-message-banner-description-color);margin-bottom:0}.eps-theme-dark .e-app-import-export-message-banner{--e-app-import-export-message-banner-heading-color:#7d7e82;--e-app-import-export-message-banner-description-color:#b4b5b7}.e-app-import-connect-pro-notice,.e-app-import-failed-plugins-notice{margin-bottom:1.25rem}.e-onboarding{font-family:DM Sans,Roboto,sans-serif}.e-onboarding .eps-app__main{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;overflow-y:auto}.e-onboarding__content{max-width:1135px;padding:initial;margin:2.75rem;overflow-y:initial}.e-onboarding__checkbox-label{display:-webkit-box;display:-ms-flexbox;display:flex;line-height:18px;margin-bottom:27px}.e-onboarding__checkbox-input{-webkit-margin-end:14px;margin-inline-end:14px;width:16px;height:16px;border-color:#69727d;border-radius:2px}.e-onboarding__checkbox-input:checked{background-color:#525861}.e-onboarding__checkbox-input:checked:after{margin-bottom:15%;width:6px;height:9px;border-width:0 2px 2px 0}.e-onboarding__feature-list{margin-bottom:40px}.e-onboarding__text-input{font-size:14px;width:325px;padding:12px 16px;color:#69727d;border:1px solid #9ea5ad}.e-onboarding__text-input:focus-visible{outline:initial;border:1px solid #3a3f45}.e-onboarding__text-input::-webkit-input-placeholder{color:#c2c7cc}.e-onboarding__text-input::-moz-placeholder{color:#c2c7cc}.e-onboarding__text-input::-ms-input-placeholder{color:#c2c7cc}.e-onboarding__text-input::placeholder{color:#c2c7cc}.e-onboarding__footnote{margin-top:24px;width:325px;text-align:center}.e-onboarding__footnote a{text-decoration:underline;color:#3a3f45}#e-app~#__wp-uploader-id-2 .media-modal{max-width:1024px;max-height:768px;margin:auto}.e-onboarding__page-content{margin-bottom:70px}.e-onboarding__page-content-start{max-width:675px;text-align:start;-ms-flex-preferred-size:555px;flex-basis:555px;-ms-flex-item-align:start;align-self:start}.e-onboarding__page-content-end{min-width:440px;max-width:540px}.e-onboarding__page-content-end img{width:100%}.e-onboarding__page-content-section-title{font-family:"DM Serif Display",serif;font-size:36px;font-weight:700;color:#0c0d0e}.e-onboarding__page-content-section-text{font-size:18px;color:#3a3f45}.e-onboarding__header-logo .eps-app__logo{background-color:#0c0d0e;color:#fff;width:1.3rem;height:1.3rem;line-height:1.3rem;font-size:.48rem}.e-onboarding__header-logo .eps-app__logo:not(:last-child){-webkit-margin-end:7px;margin-inline-end:7px}.e-onboarding__header-logo img{width:104px}.e-onboarding__header .eps-app__header-btn{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:13px}.e-onboarding__header .eps-app__header-btn .eps-icon:not(:last-child){-webkit-margin-end:7px;margin-inline-end:7px}.e-onboarding__header .eps-button{color:#0c0d0e}.e-onboarding__header .eps-button__go-pro-btn{background-color:#871a40;color:#fff;padding:4px 8px;border-radius:4px;font-weight:700;font-size:12px;-webkit-transition:.5s;-o-transition:.5s;transition:.5s}.e-onboarding__header .eps-button__go-pro-btn:hover{background:#b22254}.e-onboarding__go-pro{width:288px;font-size:12px}.e-onboarding__go-pro-title{font-size:18px;font-weight:700;color:#871a40}.e-onboarding__go-pro-cta{display:inline-block;color:#871a40;padding:9px;border:1px solid #871a40}.e-onboarding__go-pro-cta.e-onboarding__button{font-size:14px}.e-onboarding__go-pro-paragraph:not(:last-child){margin-bottom:20px}.e-onboarding__go-pro-already-have{text-decoration:underline}.e-onboarding__progress-bar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-bottom:125px}.e-onboarding__progress-bar-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#69727d;font-size:.75rem}.e-onboarding__progress-bar-item-icon{display:inline-block;font-family:"DM Serif Display",serif;text-align:center;width:1.1rem;height:1.1rem;line-height:1rem;font-size:.75rem;font-weight:700;border-radius:50%;border:1px solid #69727d;-webkit-margin-end:8px;margin-inline-end:8px;-ms-flex-negative:0;flex-shrink:0}.e-onboarding__progress-bar-item:not(:last-child){-webkit-margin-end:22px;margin-inline-end:22px}.e-onboarding__progress-bar-item:not(:last-child):after{font-family:eicons;-webkit-margin-start:22px;margin-inline-start:22px;content:"\e89e"}.e-onboarding__progress-bar-item--active{color:#040080}.e-onboarding__progress-bar-item--active .e-onboarding__progress-bar-item-icon,.e-onboarding__progress-bar-item--completed .e-onboarding__progress-bar-item-icon{color:#fff;border-color:#040080;background-color:#040080}.e-onboarding__progress-bar-item--completed,.e-onboarding__progress-bar-item--skipped{cursor:pointer}.e-onboarding__progress-bar-item--skipped .e-onboarding__progress-bar-item-icon{color:#fff;background-color:#69727d}.e-onboarding__button{font-size:18px;cursor:pointer}.e-onboarding__button-action{color:#46f2b6;background-color:#040080;width:325px;padding:15px;text-align:center}.e-onboarding__button-skip{color:#6a727c;padding:15px 29px}.e-onboarding__button--disabled{pointer-events:none;background-color:#d5d8dc;color:#9ea5ad}.e-onboarding__button--disabled:hover{cursor:progress}.e-onboarding__button--processing{pointer-events:none;-webkit-filter:brightness(90%);filter:brightness(90%)}.e-onboarding__card{border:1px solid #3a3f45;border-radius:8px;padding:40px;background-color:#fff;cursor:pointer;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.e-onboarding__card-image,.e-onboarding__card-text{width:345px}.e-onboarding__card-image{margin-bottom:44px}.e-onboarding__card-text{font-size:20px;font-weight:700;text-align:center;color:#0c0d0e}.e-onboarding__card:hover{-webkit-box-shadow:4px 4px 0 0 #000;box-shadow:4px 4px 0 0 #000}.e-onboarding__card:active{-webkit-box-shadow:initial;box-shadow:none}.e-onboarding__checklist{-webkit-padding-start:0;padding-inline-start:0}.e-onboarding__checklist-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:12px;margin-bottom:12px}.e-onboarding__checklist-item .eicon-check-circle{-webkit-margin-end:9px;margin-inline-end:9px;font-size:1