localtower 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +121 -33
  3. data/app/assets/stylesheets/application.css +68 -0
  4. data/app/controllers/localtower/pages_controller.rb +20 -35
  5. data/app/javascript/application.js +46 -0
  6. data/app/javascript/components/DarkSelect.js +115 -0
  7. data/app/javascript/components/ModernTooltip.js +26 -0
  8. data/app/javascript/components/NewMigrationForm.js +687 -0
  9. data/app/javascript/components/NewModelForm.js +432 -0
  10. data/app/javascript/components/data/Stores.js +294 -0
  11. data/app/views/layouts/localtower/application.html.erb +80 -66
  12. data/app/views/localtower/_migration.html.erb +30 -0
  13. data/app/views/localtower/pages/_alert_no_models.html.erb +1 -1
  14. data/app/views/localtower/pages/migrations.html.erb +24 -55
  15. data/app/views/localtower/pages/new_migration.html.erb +33 -96
  16. data/app/views/localtower/pages/new_model.html.erb +17 -83
  17. data/config/routes.rb +1 -1
  18. data/lib/localtower/engine.rb +5 -2
  19. data/lib/localtower/generators/migration.rb +218 -91
  20. data/lib/localtower/generators/model.rb +29 -20
  21. data/lib/localtower/generators/service_objects/insert_array.rb +5 -3
  22. data/lib/localtower/generators/service_objects/insert_defaults.rb +73 -3
  23. data/lib/localtower/generators/service_objects/insert_foreign_keys.rb +35 -0
  24. data/lib/localtower/generators/service_objects/insert_indexes.rb +41 -18
  25. data/lib/localtower/generators/service_objects/insert_nullable.rb +5 -3
  26. data/lib/localtower/status.rb +3 -1
  27. data/lib/localtower/tools.rb +20 -9
  28. data/lib/localtower/version.rb +1 -1
  29. data/lib/localtower.rb +0 -6
  30. data/public/com/favicon-64.png +0 -0
  31. data/public/com/twitter-cover-1.png +0 -0
  32. data/public/com/twitter-cover-2.png +0 -0
  33. data/public/fonts/Circular/CircularStd-Black.otf +0 -0
  34. data/public/fonts/Circular/CircularStd-BlackItalic.otf +0 -0
  35. data/public/fonts/Circular/CircularStd-Bold.otf +0 -0
  36. data/public/fonts/Circular/CircularStd-BoldItalic.otf +0 -0
  37. data/public/fonts/Circular/CircularStd-Book.otf +0 -0
  38. data/public/fonts/Circular/CircularStd-BookItalic.otf +0 -0
  39. data/public/fonts/Circular/CircularStd-Light Italic.otf +0 -0
  40. data/public/fonts/Circular/CircularStd-Light.otf +0 -0
  41. data/public/fonts/Circular/CircularStd-Medium.otf +0 -0
  42. data/public/fonts/Circular/CircularStd-MediumItalic.otf +0 -0
  43. data/public/fonts/Circular/Read Me.txt +5 -0
  44. data/public/fonts/Circular/www.dfonts.org.url +2 -0
  45. data/public/javascripts/application.js +30830 -0
  46. data/public/javascripts/application.js.map +7 -0
  47. data/public/screenshots/v0.1.1/1_schema.png +0 -0
  48. data/public/screenshots/v0.1.1/2_models_1.png +0 -0
  49. data/public/screenshots/v0.1.1/2_models_2.png +0 -0
  50. data/public/screenshots/v0.1.1/3_relations.png +0 -0
  51. data/public/screenshots/v0.1.1/4_migrations.png +0 -0
  52. data/public/screenshots/v0.1.6/1_schema.png +0 -0
  53. data/public/screenshots/v0.1.6/2_models_1.png +0 -0
  54. data/public/screenshots/v0.1.6/3_relations.png +0 -0
  55. data/public/screenshots/v0.1.6/4_migrations.png +0 -0
  56. data/public/screenshots/v0.1.6/5_capture.png +0 -0
  57. data/public/screenshots/v1.0.0/migrations.png +0 -0
  58. data/public/screenshots/v1.0.0/models.png +0 -0
  59. data/public/screenshots/v1.0.0/new_migration.png +0 -0
  60. data/public/screenshots/v1.0.0/new_model.png +0 -0
  61. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.44.30.jpg +0 -0
  62. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.44.42.jpg +0 -0
  63. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.44.54.jpg +0 -0
  64. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.48.21.jpg +0 -0
  65. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.48.28.jpg +0 -0
  66. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.48.51.jpg +0 -0
  67. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.49.09.jpg +0 -0
  68. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.50.04.jpg +0 -0
  69. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.50.11.jpg +0 -0
  70. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.50.19.jpg +0 -0
  71. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.51.47.jpg +0 -0
  72. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.51.48.jpg +0 -0
  73. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.52.02.jpg +0 -0
  74. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.52.18.jpg +0 -0
  75. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.52.26.jpg +0 -0
  76. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.52.38.jpg +0 -0
  77. data/public/screenshots/v2.0.0/migrations.png +0 -0
  78. data/public/screenshots/v2.0.0/new_migration.png +0 -0
  79. data/public/screenshots/v2.0.0/new_model.png +0 -0
  80. data/public/stylesheets/application.css +1179 -0
  81. data/public/vendor/jquery-3.7.1.min.js +2 -0
  82. data/public/vendor/lucide.min.js +12 -0
  83. data/public/vendor/monokai-sublime.css +80 -0
  84. data/public/vendor/rails-ujs.min.js +8 -0
  85. data/spec/dummy/Gemfile +1 -1
  86. data/spec/dummy/Gemfile.lock +140 -102
  87. data/spec/dummy/app/models/user.rb +0 -1
  88. data/spec/dummy/config/database.yml +4 -6
  89. data/spec/dummy/config/puma.rb +1 -1
  90. data/spec/dummy/db/migrate/20241118233214_create_users.rb +9 -0
  91. data/spec/dummy/db/migrate/20241118233233_add_column_titme_for_user.rb +6 -0
  92. data/spec/dummy/db/schema.rb +3 -19
  93. data/spec/dummy/lib/tasks/reset.rake +16 -0
  94. data/spec/dummy/log/development.log +20995 -15969
  95. data/spec/dummy/log/localtower.log +588 -1906
  96. data/spec/dummy/log/test.log +716 -0
  97. data/spec/dummy/tmp/cache/sessions/localtower/726/FE1/_session_id%3A2%3A%3A69d6764784f832ea25700ea2f242210f3f8d94b48912b0f09ffc44311f0376eb +0 -0
  98. data/spec/dummy/tmp/cache/sessions/localtower/77E/121/_session_id%3A2%3A%3A0b3c1fe7154461ba023ba6c81dd1b999348757880b505f4b082faed263a627f9 +1 -0
  99. data/spec/dummy/tmp/cache/sessions/localtower/7EB/8A1/_session_id%3A2%3A%3Af88237c1d157546a5dcde894fd6c1410162a8eba318748d4ac573ee75e40df16 +0 -0
  100. data/spec/dummy/tmp/local_secret.txt +1 -0
  101. data/spec/dummy/tmp/pids/server.pid +1 -1
  102. data/spec/factories/migration.rb +77 -40
  103. data/spec/factories/model.rb +46 -26
  104. data/spec/lib/localtower/generators/migration_spec.rb +16 -24
  105. data/spec/lib/localtower/generators/model_spec.rb +71 -8
  106. data/spec/lib/localtower/generators/service_objects/insert_array_spec.rb +1 -1
  107. data/spec/lib/localtower/generators/service_objects/insert_defaults_spec.rb +1 -1
  108. data/spec/lib/localtower/generators/service_objects/insert_foreign_keys_spec.rb +101 -0
  109. data/spec/lib/localtower/generators/service_objects/insert_indexes_spec.rb +1 -1
  110. data/spec/lib/localtower/generators/service_objects/insert_nullable_spec.rb +2 -2
  111. data/spec/spec_helper.rb +2 -3
  112. metadata +111 -234
  113. data/app/views/localtower/pages/models.html.erb +0 -63
  114. data/public/css/app.css +0 -55
  115. data/public/js/app.js +0 -337
  116. data/public/light-bootstrap-dashboard-master/assets/css/animate.min.css +0 -6
  117. data/public/light-bootstrap-dashboard-master/assets/css/bootstrap.min.css +0 -5
  118. data/public/light-bootstrap-dashboard-master/assets/css/demo.css +0 -61
  119. data/public/light-bootstrap-dashboard-master/assets/css/light-bootstrap-dashboard.css +0 -2789
  120. data/public/light-bootstrap-dashboard-master/assets/css/pe-icon-7-stroke.css +0 -632
  121. data/public/light-bootstrap-dashboard-master/assets/fonts/Pe-icon-7-stroke.eot +0 -0
  122. data/public/light-bootstrap-dashboard-master/assets/fonts/Pe-icon-7-stroke.svg +0 -212
  123. data/public/light-bootstrap-dashboard-master/assets/fonts/Pe-icon-7-stroke.ttf +0 -0
  124. data/public/light-bootstrap-dashboard-master/assets/fonts/Pe-icon-7-stroke.woff +0 -0
  125. data/public/light-bootstrap-dashboard-master/assets/img/default-avatar.png +0 -0
  126. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-0.jpg +0 -0
  127. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-1.jpg +0 -0
  128. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-2.jpg +0 -0
  129. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-3.jpg +0 -0
  130. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-4.jpg +0 -0
  131. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-5.jpg +0 -0
  132. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-6.jpg +0 -0
  133. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-7.jpg +0 -0
  134. data/public/light-bootstrap-dashboard-master/assets/img/faces/tim_vector.jpe +0 -0
  135. data/public/light-bootstrap-dashboard-master/assets/img/favicon.ico +0 -0
  136. data/public/light-bootstrap-dashboard-master/assets/img/loading-bubbles.svg +0 -14
  137. data/public/light-bootstrap-dashboard-master/assets/img/mask.png +0 -0
  138. data/public/light-bootstrap-dashboard-master/assets/img/new_logo.png +0 -0
  139. data/public/light-bootstrap-dashboard-master/assets/img/sidebar-1.jpg +0 -0
  140. data/public/light-bootstrap-dashboard-master/assets/img/sidebar-2.jpg +0 -0
  141. data/public/light-bootstrap-dashboard-master/assets/img/sidebar-3.jpg +0 -0
  142. data/public/light-bootstrap-dashboard-master/assets/img/sidebar-4.jpg +0 -0
  143. data/public/light-bootstrap-dashboard-master/assets/img/sidebar-5.jpg +0 -0
  144. data/public/light-bootstrap-dashboard-master/assets/img/tim_80x80.png +0 -0
  145. data/public/light-bootstrap-dashboard-master/assets/js/bootstrap-checkbox-radio-switch.js +0 -502
  146. data/public/light-bootstrap-dashboard-master/assets/js/bootstrap-notify.js +0 -404
  147. data/public/light-bootstrap-dashboard-master/assets/js/bootstrap-select.js +0 -438
  148. data/public/light-bootstrap-dashboard-master/assets/js/bootstrap.min.js +0 -7
  149. data/public/light-bootstrap-dashboard-master/assets/js/chartist.min.js +0 -9
  150. data/public/light-bootstrap-dashboard-master/assets/js/demo.js +0 -152
  151. data/public/light-bootstrap-dashboard-master/assets/js/jquery-1.10.2.js +0 -9789
  152. data/public/light-bootstrap-dashboard-master/assets/js/light-bootstrap-dashboard.js +0 -179
  153. data/public/logo-localtower-white-300.png +0 -0
  154. data/public/logo-localtower-white.png +0 -0
  155. data/public/logo-localtower.png +0 -0
  156. data/public/vendor/font-awesome.min.css +0 -4
  157. data/public/vendor/highlight-js-default.min.css +0 -9
  158. data/spec/dummy/app/models/post.rb +0 -3
  159. data/spec/dummy/db/migrate/20230119221452_create_users.rb +0 -14
  160. data/spec/dummy/db/migrate/20230119221751_change_users_at1674166670.rb +0 -7
  161. data/spec/dummy/db/migrate/20230119222054_create_posts.rb +0 -11
  162. data/spec/dummy/db/migrate/20230119222106_change_posts_at1674166865.rb +0 -5
  163. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/-Y/-YOiiBKqc2UODHFjctm8xc7xFoZaL7zOjWQj6qQ2wyE.cache +0 -1
  164. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/-d/-dwueM4vmPt8L51S3jeSyg_AjGDcj0GUN6pDpCA1gCg.cache +0 -3
  165. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/0g/0gaJnJQdtd2ACbihXxn8OnjLWlDjnQ_WxfgOpbiLzhg.cache +0 -1
  166. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/0r/0rFCsCV9kZnEYtZZ6sfig8329OU31bqjecDFqSVank8.cache +0 -1
  167. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/1J/1J2k_CpnQE3d-PZAQwOVGQALGkta4qVvhdsKjgG0e4Q.cache +0 -0
  168. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/2p/2pYVH2Z_syqh6ok8QYxJNKxXpx1Iwppf6JGElZI0gpw.cache +0 -0
  169. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/3W/3WBmqd-2V6q5N-jvbyp-tlcfn3aHYMwBppbOUm7x4qg.cache +0 -1
  170. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/4K/4KFPlHkhdDW0riGUmlbaR-kmDz6JUnQvY6fwW8qsdaE.cache +0 -3
  171. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/6x/6xMeRWmLpNK_flx6-JA3KazvUxSCxyPxHv9Zm3eC26Q.cache +0 -1
  172. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/7d/7dNqI_dCDJLJmI1oM4xwFp9nRRNOem-4P4OD7PMyz2E.cache +0 -0
  173. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/8X/8XquhxVcp5A8QquLtxO8NgKTMJch0eqQmzFmRGIZP6I.cache +0 -1
  174. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Ah/AhdfXXtU63kXS4TnsH2Vi1yWgVkIEeV8Z8XA68hrNQc.cache +0 -0
  175. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Bt/BtAePnwLSGw82xUGI7wuhWDfuwarOQVS91YqCsweMcs.cache +0 -0
  176. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Bx/BxzoG0KxeNLac4xTNeJv3qfeytbNBw58xj2zD-xdbrE.cache +0 -2
  177. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/CC/CCD0ROPX0yxHwNpVZmuP8ZNtgQpaVCMXpdzC0Wb5n24.cache +0 -1
  178. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/DS/DS43oxBg6K5PMWLn2mTy_4EnxI03ehHkxpjV7NCi8yM.cache +0 -1
  179. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Do/Do83AoOat5W-c1g7piDGy2GMmffsY6JY0Qfuh5PY3GM.cache +0 -2
  180. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/EI/EI0Nxk-VRATWstMuVCVc0_GdYlQ-a0dx6n2g0l3vIik.cache +0 -0
  181. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/EN/ENhJnzJAU2IK-7aHqubj9N8Jo_UTmjG_VEQqyIkolQ8.cache +0 -1
  182. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Fc/FcS6VUHN3Bd4pHGqd5NHA8jA4OLwWrU94s3b4GGxX9c.cache +0 -1
  183. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/G0/G0kukI-r0q0Vbrg6e_jnYJoYcOX8K__h-mwKbCf4twE.cache +0 -0
  184. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/I1/I1Jw08-mz8xzgrgi6giCzpf1UmzGTSbl4eJEw4DAoJM.cache +0 -1
  185. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/IV/IVOO1dXBmgjieDk__g57p6aYt0Z3CmfTa32jhegoyko.cache +0 -3
  186. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/JY/JYPmv5WP4wxdI9EKEEPkK_fVtqeSLfmint_5E8fQLQ8.cache +0 -1
  187. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/MX/MXI3KxaQPZGImSvCNZ_TbQVruWCJ3E0xiVxza1ZCAjM.cache +0 -1
  188. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/OU/OUoioCqXALK909jXPV3VSyCJIdNC7bsogUfdnRTpc5o.cache +0 -1
  189. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/PH/PHjtqNLUPAUDiSlu5AbPjlIo20mOGNm0uNjMLhX2NvM.cache +0 -0
  190. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/RC/RC9vfALY5K634pTeau0BAhTHl7d5_5yA3tM-QTLiKtI.cache +0 -0
  191. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/RG/RG1PrmlixwaUlG8BV0kcm_3F7OQekxsrLYjBdf403-k.cache +0 -2
  192. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Ut/UtoMO6n6FHTpRCGk9VfxlfTI2Ao2GYJ_6kMzx3B9VH8.cache +0 -1
  193. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/VE/VExnbpDk3LxYdPk3htUrZQXPI8NK_zlKtSFfVXJlxU4.cache +0 -1
  194. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Wm/WmKTjykiU-Tx_Faf5zduEeEQ-DozAs5omKToM2l1cZM.cache +0 -0
  195. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/X-/X-KjhDPd0nI155N_FaxFSgaOiYo0_ytqlmkBlLM7ayw.cache +0 -1
  196. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/XX/XXVhj-O91tJ5c8pz7DQlTABOv8TIRVg5haw9VOq9HuI.cache +0 -1
  197. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/YC/YCiifo5NCMwDChFqYFiV4EaYEx8hy3Efle1PsGbIdak.cache +0 -1
  198. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Ya/YayMISAqD-Y3vBFywwKrXbUovGf5o77HUF5s8mnQgO0.cache +0 -0
  199. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Zl/Zl-NEb0aDLmZ9gN4gdY1OYlbUXD4JhkFY_y23m55zmo.cache +0 -0
  200. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/_h/_hnjPptBeD3GP5B0iJR6pvyVkiMiq-o2TxaYmGf9KJo.cache +0 -3
  201. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/_p/_poEF6QbTPYJiQCFGoXxIXjmvcn6T0I87ElzJGHguk0.cache +0 -1
  202. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/aa/aaSj2zt8ddc87nZqTpOI9gRCKVnoalXKFBww7t4t3Kg.cache +0 -0
  203. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/bZ/bZHWUkrOLp0WM_Ogo36Qjt4cxDt-rOFgtcj4LNB-BVE.cache +0 -1
  204. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/cP/cPSNG_663TNT944Rke919luRS6FtpMwVc_7aDph645w.cache +0 -1
  205. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/ce/ceq8fcZGIoqWrCl9vrCRrywEdx42iy9PrGG9CwFSIF4.cache +0 -0
  206. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/cl/cljhP0Jc018nLnG1rpdg-FUf6mlIqrQYvXTNXTNG9Bw.cache +0 -0
  207. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/dm/dmod3fTZdoKso2FGM5RTQtb4kTB6vywsiXLWDygYFN4.cache +0 -1
  208. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/dy/dySDGcUTqlKx2MTvOmIEP-WzhyhCfU5xhN9qgxbz3rI.cache +0 -0
  209. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/gQ/gQYdmEzbLjTcFCnsfzXzEmeminOchF263snAk3IvXM0.cache +0 -1
  210. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/gh/ghvuMAqccBGljlmEaI3Gt5tH5rEg6hseKYMYfRMkHdY.cache +0 -1
  211. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/gt/gt8QC7k92A4hXA3HlCpQulW73tqd6x63I3uT-YrGMj0.cache +0 -1
  212. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/hf/hfVg-y7dPSQKbb7V3I7M5w7IpOhDiIB2H31d2tmbZMU.cache +0 -0
  213. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/iF/iFLwyhZIu1Jxm77NUT2qWeTDMmyELW4U85t9rGE_KVg.cache +0 -0
  214. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/kF/kFlIcBBnSyKE00OjpM90pqSn93mLMUv9esz86nrO-Cs.cache +0 -1
  215. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/ma/MazNt_5OaYexRuZnccZZ_7AONlxn1a4W3KdWyHyBdws.cache +0 -0
  216. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/ma/ma3w08gnDTeqwY2-C9BlToxA6-AS8bvXlu-nBiz0UYs.cache +0 -1
  217. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/n_/n_xYqQYhwEMQknb3jFQnjlxxBE9TzMNHCdJ-bEyZFIw.cache +0 -2
  218. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/nx/nxTv3sKVUQZADJyM3dPaVmUA78MIsMLD_K279yN_GsI.cache +0 -2
  219. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/pv/pv6tV3CDkIAmLXdU8EPLlNEkXAKJPufVD4VP30o4fWo.cache +0 -0
  220. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/qP/qPmv5snMrDw830S6hSICDcnIy7kVEWoFKXhGKT38lG4.cache +0 -2
  221. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/sY/sYvTb1uPWWmnHJNXKuW_xSco-aUb-rN2f0J35zSIzuc.cache +0 -1
  222. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/su/suQ_jjDxRiIn4VEqFJYKaBWJILaeGKBvoTv49XNX0vo.cache +0 -1
  223. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/u8/u8IrW5IIQ7espwk2Vpk23zheL9YZA0tnbKq9X7E-WA0.cache +0 -1
  224. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/wh/whzETExjcZCkn4msasD2aylgfhfpKPZrNxTkY-SOIDg.cache +0 -2
  225. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/x7/x7PYh8DJvPykcEqpVab2vcY9-GFz-3cqtoMlRAu94Uc.cache +0 -2
  226. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/y6/y6oxsJD3pAY9ph1_5M-77uiEjTVw8BheLZNygCKPm1g.cache +0 -1
  227. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/yH/yHZ-a1J23ZCf2n5mEHINMz23Iec2cLGTKauW7k4yGTE.cache +0 -0
  228. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/z7/z7KfUWYoSaGq8tQ5mbHwyfXyT3Pa4FWKJswHcIdJuds.cache +0 -1
  229. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/zu/zuExWc5WHxeOUPZUKHl9a9ZRmN50g7jMOD28macn6M0.cache +0 -0
@@ -0,0 +1,687 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { X, Plus, TableOfContents } from "lucide-react";
3
+ // import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
4
+
5
+ import DarkSelect from "./DarkSelect.js";
6
+ import ModernTooltip from "./ModernTooltip.js";
7
+ import Stores from "./data/Stores.js";
8
+
9
+ const NewMigrationForm = () => {
10
+ const COLUMN_DEFAULTS = window.COLUMN_DEFAULTS;
11
+
12
+ const VISIBLE_FIELDS = {
13
+ add_column: [
14
+ "model_name",
15
+ "action_name",
16
+ "column_name",
17
+ "column_type",
18
+ "default",
19
+ "nullable",
20
+ "unique",
21
+ "index",
22
+ "index_algorithm",
23
+ "foreign_key",
24
+ ],
25
+ remove_column: ["model_name", "action_name", "list_attributes"],
26
+ rename_column: [
27
+ "model_name",
28
+ "action_name",
29
+ "list_attributes",
30
+ "new_column_name",
31
+ ],
32
+ change_column_type: [
33
+ "model_name",
34
+ "action_name",
35
+ "list_attributes",
36
+ "new_column_type",
37
+ ],
38
+ belongs_to: ["model_name", "action_name", "list_models", "foreign_key"],
39
+ add_index_to_column: [
40
+ "model_name",
41
+ "action_name",
42
+ "list_attributes",
43
+ "unique",
44
+ "index",
45
+ "index_algorithm",
46
+ ],
47
+ remove_index_to_column: ["model_name", "action_name", "list_indexes"],
48
+ drop_table: ["model_name", "action_name"],
49
+ };
50
+
51
+ const DEFAULT_LINE = {
52
+ table_name: Stores().APP_MODELS[0] ? Stores().APP_MODELS[0].table_name : "",
53
+ model_name: Stores().APP_MODELS[0] ? Stores().APP_MODELS[0].underscore : "",
54
+ action_name: Stores().actionsForSelect()[0]
55
+ ? Stores().actionsForSelect()[0].value
56
+ : "",
57
+ column_type: "string",
58
+ column_name: "",
59
+ new_column_name: "",
60
+ new_column_type: "",
61
+ default: "",
62
+ unique: false,
63
+ foreign_key: false,
64
+ index: "",
65
+ index_algorithm: "default",
66
+ index_name: "",
67
+ nullable: true,
68
+ };
69
+
70
+ const [formRows, setFormRows] = useState([DEFAULT_LINE]);
71
+ const [showOptions, setShowOptions] = useState({});
72
+
73
+ const shouldBeVisible = (row, td_name) => {
74
+ return VISIBLE_FIELDS[row.action_name].includes(td_name) ? "block" : "none";
75
+ };
76
+
77
+ const handleAddRow = (event) => {
78
+ event.preventDefault();
79
+
80
+ setFormRows([...formRows, { ...DEFAULT_LINE }]);
81
+ };
82
+
83
+ const handleDeleteRow = (index, event) => {
84
+ event.preventDefault();
85
+
86
+ // Don't delete if there is only one row:
87
+ if (formRows.length === 1) {
88
+ return;
89
+ }
90
+
91
+ setFormRows(formRows.filter((row, rowIndex) => rowIndex !== index));
92
+ };
93
+
94
+ const handleInputChange = (index, event) => {
95
+ const { name, value } = event.target;
96
+ const updatedRows = [...formRows];
97
+
98
+ let newValue = value;
99
+
100
+ // Sanitise default input:
101
+ if (name === "column_name") {
102
+ newValue = Stores().snakeCase(newValue);
103
+ newValue = newValue.trim();
104
+ }
105
+
106
+ updatedRows[index][name] = newValue;
107
+
108
+ if (name === "model_name") {
109
+ updatedRows[index].table_name =
110
+ Stores().modelByValue(newValue).table_name;
111
+ updatedRows[index].column_name = "";
112
+ updatedRows[index].index_name = "";
113
+ updatedRows[index].index = "";
114
+ updatedRows[index].index_algorithm = "";
115
+ updatedRows[index].default = "";
116
+ }
117
+
118
+ if (name === "action_name") {
119
+ updatedRows[index].column_name = "";
120
+ updatedRows[index].index_name = "";
121
+ }
122
+
123
+ if (name === "action_name" && newValue === "add_index_to_column") {
124
+ updatedRows[index].index = "default";
125
+ }
126
+
127
+ // Reset default if change.
128
+ if (name === "column_type") {
129
+ // result index:
130
+ // result default:
131
+ updatedRows[index].index = "";
132
+ updatedRows[index].default = "";
133
+ // close modal:
134
+ setShowOptions({ [index]: false });
135
+ }
136
+
137
+ if (name === "default" && newValue !== "") {
138
+ updatedRows[index].nullable = false;
139
+ }
140
+
141
+ // For the selector of the referenced model:
142
+ if (
143
+ name === "column_type" &&
144
+ value === "references" &&
145
+ Stores().APP_MODELS[0]
146
+ ) {
147
+ updatedRows[index].column_name = Stores().APP_MODELS[0].underscore;
148
+ updatedRows[index].foreign_key = true;
149
+ }
150
+
151
+ if (
152
+ updatedRows[index].column_type !== "references" &&
153
+ updatedRows[index].foreign_key === true
154
+ ) {
155
+ updatedRows[index].foreign_key = false;
156
+ }
157
+
158
+ // Reset unique if no index is selected:
159
+ if (
160
+ name === "index" &&
161
+ newValue === "" &&
162
+ updatedRows[index].unique === true
163
+ ) {
164
+ updatedRows[index].unique = false;
165
+ }
166
+
167
+ setFormRows(updatedRows);
168
+ };
169
+
170
+ const handleCheckboxChange = (index, event) => {
171
+ const { name, checked, value } = event.target;
172
+ const updatedRows = [...formRows];
173
+
174
+ let finalValue = checked;
175
+
176
+ if (name === "nullable" && checked && updatedRows[index].default !== "") {
177
+ return false;
178
+ }
179
+
180
+ if (name === "unique" && checked && updatedRows[index].index === "") {
181
+ updatedRows[index].index = "default";
182
+ }
183
+
184
+ if (
185
+ name === "index_algorithm" &&
186
+ checked &&
187
+ updatedRows[index].index != ""
188
+ ) {
189
+ finalValue = value === "concurrently" ? "concurrently" : "default";
190
+ }
191
+
192
+ updatedRows[index][name] = finalValue;
193
+
194
+ setFormRows(updatedRows);
195
+ };
196
+
197
+ const handleOptionClick = (index, option) => {
198
+ const updatedRows = [...formRows];
199
+ updatedRows[index].default = option.value;
200
+
201
+ if (updatedRows[index].default !== "") {
202
+ updatedRows[index].nullable = false;
203
+ } else {
204
+ updatedRows[index].nullable = true;
205
+ }
206
+
207
+ setFormRows(updatedRows);
208
+ setShowOptions({ [index]: false });
209
+ };
210
+
211
+ const handleShowOptions = (index, event) => {
212
+ event.preventDefault();
213
+
214
+ if (showOptions[index]) {
215
+ setShowOptions({ [index]: false });
216
+ } else {
217
+ setShowOptions({ [index]: true });
218
+ }
219
+ };
220
+
221
+ const addToForm = () => {
222
+ // Add the form to the hidden input:
223
+ document.getElementById("form_attributes").value = JSON.stringify(formRows);
224
+ };
225
+
226
+ useEffect(() => {
227
+ addToForm();
228
+ }, [formRows]); // This effect depends on formRows
229
+
230
+ if (Stores().APP_MODELS.length === 0) {
231
+ return <div className="flex flex-col gap-2"></div>;
232
+ }
233
+
234
+ return (
235
+ <div>
236
+ <table className="w-full max-w-screen-xl">
237
+ <tbody className="border-b border-localtower-500">
238
+ {formRows.map((row, index) => (
239
+ <tr key={`$row-${index}`} className="pb-2">
240
+ <td className="w-40 py-2 pr-3">
241
+ {/* ============== */}
242
+ {/* LIST MODELS / TABLES */}
243
+ <div className="w-40">
244
+ <div className="text-xs text-localtower-450 pb-0.5 font-medium">
245
+ Model
246
+ </div>
247
+ <DarkSelect
248
+ keySuffix={"model_name" + row.table_name}
249
+ options={Stores().modelsForSelect()}
250
+ defaultValue={Stores().modelsForSelect()[0]}
251
+ value={row.model_name}
252
+ onChange={(selectedOption) => {
253
+ if (selectedOption && selectedOption.value) {
254
+ const syntheticEvent = {
255
+ target: {
256
+ name: "model_name",
257
+ value: selectedOption.value,
258
+ },
259
+ };
260
+ handleInputChange(index, syntheticEvent);
261
+ }
262
+ }}
263
+ />
264
+ </div>
265
+ </td>
266
+ <td className="w-56 py-2 pr-3">
267
+ {/* ============== */}
268
+ {/* LIST ACTIONS */}
269
+ <div className="w-56">
270
+ <div className="text-xs text-localtower-450 pb-0.5 font-medium">
271
+ Action
272
+ </div>
273
+ <DarkSelect
274
+ keySuffix={"action_name" + row.table_name}
275
+ options={Stores().actionsForSelect()}
276
+ defaultValue={Stores().actionsForSelect()[0]}
277
+ customClassNamesOverrides={{ menu: "!w-[450px]" }}
278
+ onChange={(selectedOption) => {
279
+ if (selectedOption && selectedOption.value) {
280
+ const syntheticEvent = {
281
+ target: {
282
+ name: "action_name",
283
+ value: selectedOption.value,
284
+ },
285
+ };
286
+ handleInputChange(index, syntheticEvent);
287
+ }
288
+ }}
289
+ />
290
+ </div>
291
+ </td>
292
+ <td className="py-2 pr-3 flex flex-row gap-2">
293
+ {/* ============== */}
294
+ {/* LIST ATTRIBUTES */}
295
+ <div
296
+ className="w-48"
297
+ style={{ display: shouldBeVisible(row, "list_attributes") }}
298
+ >
299
+ <div className="text-xs text-localtower-450 pb-0.5 font-medium">
300
+ Column
301
+ </div>
302
+ <DarkSelect
303
+ keySuffix={"column_name" + row.table_name}
304
+ options={Stores().attributesForSelect(
305
+ Stores().filterAttributes(row)
306
+ )}
307
+ defaultValue=""
308
+ onChange={(selectedOption) => {
309
+ const syntheticEvent = {
310
+ target: {
311
+ name: "column_name",
312
+ value: selectedOption.value,
313
+ },
314
+ };
315
+ handleInputChange(index, syntheticEvent);
316
+ }}
317
+ />
318
+ </div>
319
+ {/* ============== */}
320
+ {/* LIST INDEXES */}
321
+ <div
322
+ className="w-80"
323
+ style={{ display: shouldBeVisible(row, "list_indexes") }}
324
+ >
325
+ <div className="text-xs text-localtower-450 pb-0.5 font-medium">
326
+ Index to remove
327
+ </div>
328
+ <DarkSelect
329
+ keySuffix={"list_indexes" + row.table_name}
330
+ options={Stores()
331
+ .filterIndexes(row)
332
+ .map((attr) => ({
333
+ value: attr.value,
334
+ label: attr.label,
335
+ }))}
336
+ defaultValue=""
337
+ onChange={(selectedOption) => {
338
+ const syntheticEvent = {
339
+ target: {
340
+ name: "index_name",
341
+ value: selectedOption.value,
342
+ },
343
+ };
344
+ handleInputChange(index, syntheticEvent);
345
+ }}
346
+ />
347
+ </div>
348
+ {/* ============== */}
349
+ {/* LIST MODELS (COLUMN NAME) */}
350
+ <div
351
+ className="w-40"
352
+ style={{ display: shouldBeVisible(row, "list_models") }}
353
+ >
354
+ <div className="text-xs text-localtower-450 pb-0.5 font-medium">
355
+ Models
356
+ </div>
357
+ <DarkSelect
358
+ keySuffix={"list_models" + row.table_name}
359
+ options={Stores().modelsForSelectWithout([row.model_name])}
360
+ value={row.column_name}
361
+ onChange={(selectedOption) => {
362
+ const syntheticEvent = {
363
+ target: {
364
+ name: "column_name",
365
+ value: selectedOption.value,
366
+ },
367
+ };
368
+ handleInputChange(index, syntheticEvent);
369
+ }}
370
+ />
371
+ </div>
372
+ {/* ============== */}
373
+ {/* COLUMN NAME */}
374
+ <div
375
+ className="w-48"
376
+ style={{ display: shouldBeVisible(row, "column_name") }}
377
+ >
378
+ <div className="text-xs text-localtower-450 pb-0.5 font-medium">
379
+ Column name
380
+ </div>
381
+ <input
382
+ type="text"
383
+ className="px-2 py-1.5 rounded bg-localtower-600 border border-localtower-500 inline-block w-full outline-none focus:border-localtower-400 transition-colors text-localtower-100"
384
+ name="column_name"
385
+ value={row.column_name}
386
+ onChange={(event) => handleInputChange(index, event)}
387
+ />
388
+ </div>
389
+ {/* ============== */}
390
+ {/* COLUMN TYPE */}
391
+ <div
392
+ className="w-48"
393
+ style={{ display: shouldBeVisible(row, "column_type") }}
394
+ >
395
+ <div className="text-xs text-localtower-450 pb-0.5 font-medium">
396
+ Column type
397
+ </div>
398
+ <DarkSelect
399
+ keySuffix={"column_type" + row.table_name}
400
+ options={Stores().typesForSelect()}
401
+ defaultValue={Stores().typesForSelect()[0]}
402
+ onChange={(selectedOption) => {
403
+ const syntheticEvent = {
404
+ target: {
405
+ name: "column_type",
406
+ value: selectedOption.value,
407
+ },
408
+ };
409
+ handleInputChange(index, syntheticEvent);
410
+ }}
411
+ />
412
+ </div>
413
+ {/* ============== */}
414
+ {/* NEW COLUMN TYPE */}
415
+ <div
416
+ className="w-40"
417
+ style={{ display: shouldBeVisible(row, "new_column_type") }}
418
+ >
419
+ <div className="text-xs text-localtower-450 pb-0.5 font-medium">
420
+ New column type
421
+ </div>
422
+ <DarkSelect
423
+ keySuffix={"new_column_type" + row.table_name}
424
+ options={Stores().typesForSelect()}
425
+ defaultValue={Stores().typesForSelect()[0]}
426
+ onChange={(selectedOption) => {
427
+ const syntheticEvent = {
428
+ target: {
429
+ name: "new_column_type",
430
+ value: selectedOption.value,
431
+ },
432
+ };
433
+ handleInputChange(index, syntheticEvent);
434
+ }}
435
+ />
436
+ </div>
437
+ {/* ============== */}
438
+ {/* DEFAULT VALUES */}
439
+ <div style={{ display: shouldBeVisible(row, "default") }}>
440
+ <div className="text-xs text-localtower-450 pb-0.5 font-medium">
441
+ Default value
442
+ </div>
443
+ <div className="relative">
444
+ <input
445
+ type="text"
446
+ name="default"
447
+ className="px-2 py-1.5 pr-8 rounded bg-localtower-600 border border-localtower-500 block w-full outline-none focus:border-localtower-400 transition-colors text-localtower-100"
448
+ value={row.default}
449
+ placeholder="NULL"
450
+ onChange={(event) => handleInputChange(index, event)}
451
+ />
452
+ {COLUMN_DEFAULTS[row.column_type] && (
453
+ <div className="absolute inset-y-0 right-0 pl-3 pr-1 flex space-x-1 items-center">
454
+ <button
455
+ className="bg-localtower-600 p-1 rounded-sm border border-localtower-500 outline-none hover:border-localtower-450 hover:bg-locatower-900 active:opacity-60 transition-colors cursor-pointer"
456
+ onClick={(event) => handleShowOptions(index, event)}
457
+ >
458
+ <TableOfContents
459
+ strokeWidth={1}
460
+ size={17}
461
+ className="text-localtower-200"
462
+ />
463
+ </button>
464
+ </div>
465
+ )}
466
+ {COLUMN_DEFAULTS[row.column_type] && showOptions[index] && (
467
+ <div
468
+ className="options-popup bg-localtower-600 border border-localtower-500 text-localtower-100 rounded flex flex-col gap-2 overflow-hidden z-30 w-60"
469
+ style={{
470
+ display: showOptions[index] ? "block" : "none",
471
+ position: "absolute",
472
+ top: "100%",
473
+ left: "0",
474
+ right: "0",
475
+ }}
476
+ >
477
+ {COLUMN_DEFAULTS[row.column_type] &&
478
+ COLUMN_DEFAULTS[row.column_type].map((option) => (
479
+ <div
480
+ key={option.value}
481
+ onClick={() => handleOptionClick(index, option)}
482
+ className="hover:bg-localtower-900 p-2 active:opacity-60 cursor-pointer"
483
+ >
484
+ {option.label}
485
+ </div>
486
+ ))}
487
+ </div>
488
+ )}
489
+ </div>
490
+ </div>
491
+ {/* ============== */}
492
+ {/* NEW COLUMN NAME */}
493
+ <div
494
+ className="w-48"
495
+ style={{ display: shouldBeVisible(row, "new_column_name") }}
496
+ >
497
+ <div className="text-xs text-localtower-450 pb-0.5 font-medium">
498
+ New column name
499
+ </div>
500
+ <input
501
+ type="text"
502
+ className="px-2 py-1.5 rounded bg-localtower-600 border border-localtower-500 inline-block w-full outline-none focus:border-localtower-400 transition-colors text-localtower-100"
503
+ name="new_column_name"
504
+ value={row.new_column_name}
505
+ onChange={(event) => handleInputChange(index, event)}
506
+ />
507
+ </div>
508
+ {/* ============== */}
509
+ {/* INDEX */}
510
+ <div
511
+ className="w-40"
512
+ style={{ display: shouldBeVisible(row, "index") }}
513
+ >
514
+ <div className="text-xs text-localtower-450 pb-0.5 font-medium">
515
+ Index
516
+ </div>
517
+ <DarkSelect
518
+ options={Stores().indexesForSelect()}
519
+ value={row.index}
520
+ customClassNamesOverrides={{ menu: "!w-40" }}
521
+ onChange={(selectedOption) => {
522
+ const syntheticEvent = {
523
+ target: {
524
+ name: "index",
525
+ value: selectedOption.value,
526
+ },
527
+ };
528
+ handleInputChange(index, syntheticEvent);
529
+ }}
530
+ />
531
+ </div>
532
+ {/* ============== */}
533
+ {/* CHECKBOXES */}
534
+ <div>
535
+ <div className="text-xs text-localtower-450 pb-0.5 font-medium">
536
+ &nbsp;
537
+ </div>
538
+ <div>
539
+ <div className="w-64 flex flex-row gap-0.5 h-8 items-center">
540
+ {row.column_type !== "references" && (
541
+ <div
542
+ style={{ display: shouldBeVisible(row, "nullable") }}
543
+ >
544
+ <ModernTooltip
545
+ content="Use the 'Nullable' option to allow null values."
546
+ title="Nullable"
547
+ >
548
+ <label
549
+ className="inline-flex items-center align-middle gap-2 px-2 py-1 rounded hover:bg-localtower-900 cursor-pointer active:opacity-60 transition-all select-none group"
550
+ style={{
551
+ opacity: row.default === "" ? 1 : 0.5,
552
+ backgroundColor: row.nullable ? "black" : "",
553
+ }}
554
+ >
555
+ <input
556
+ type="checkbox"
557
+ name="nullable"
558
+ checked={row.nullable}
559
+ onChange={(event) =>
560
+ handleCheckboxChange(index, event)
561
+ }
562
+ />
563
+ <span>NULL</span>
564
+ </label>
565
+ </ModernTooltip>
566
+ </div>
567
+ )}
568
+ <div
569
+ style={{ display: shouldBeVisible(row, "foreign_key") }}
570
+ >
571
+ <ModernTooltip
572
+ content="Use the 'Foreign Key' option to create a foreign key to another table."
573
+ title="Foreign Key"
574
+ >
575
+ <label
576
+ className="inline-flex items-center align-middle gap-2 px-2 py-1 rounded hover:bg-localtower-900 cursor-pointer active:opacity-60 transition-all select-none"
577
+ style={{
578
+ backgroundColor: row.foreign_key ? "black" : "",
579
+ }}
580
+ >
581
+ <input
582
+ type="checkbox"
583
+ name="foreign_key"
584
+ checked={row.foreign_key}
585
+ onChange={(event) =>
586
+ handleCheckboxChange(index, event)
587
+ }
588
+ />
589
+ <span>FK</span>
590
+ </label>
591
+ </ModernTooltip>
592
+ </div>
593
+ {row.column_type !== "references" && (
594
+ <div
595
+ style={{ display: shouldBeVisible(row, "unique") }}
596
+ >
597
+ <ModernTooltip
598
+ content="Use the 'Unique' option to create a unique index."
599
+ title="Index Unique"
600
+ >
601
+ <label
602
+ className="inline-flex items-center align-middle gap-2 px-2 py-1 rounded hover:bg-localtower-900 cursor-pointer active:opacity-60 transition-all select-none"
603
+ style={{
604
+ backgroundColor: row.unique ? "black" : "",
605
+ }}
606
+ >
607
+ <input
608
+ type="checkbox"
609
+ name="unique"
610
+ checked={row.unique}
611
+ onChange={(event) =>
612
+ handleCheckboxChange(index, event)
613
+ }
614
+ />
615
+ <span>IU</span>
616
+ </label>
617
+ </ModernTooltip>
618
+ </div>
619
+ )}
620
+ <div
621
+ style={{
622
+ display: shouldBeVisible(row, "index_algorithm"),
623
+ }}
624
+ >
625
+ <ModernTooltip
626
+ content="Use the 'Index Concurrently' option to create indexes without blocking writes to the table."
627
+ title="Index Concurrently"
628
+ >
629
+ <label
630
+ className="inline-flex items-center align-middle gap-2 px-2 py-1 rounded hover:bg-localtower-900 cursor-pointer active:opacity-60 transition-all select-none"
631
+ style={{
632
+ backgroundColor:
633
+ row.index_algorithm === "concurrently"
634
+ ? "black"
635
+ : "",
636
+ opacity: row.index != "" ? 1 : 0,
637
+ }}
638
+ >
639
+ <input
640
+ type="checkbox"
641
+ name="index_algorithm"
642
+ value="concurrently"
643
+ checked={row.index_algorithm === "concurrently"}
644
+ onChange={(event) =>
645
+ handleCheckboxChange(index, event)
646
+ }
647
+ />
648
+ <span>IC</span>
649
+ </label>
650
+ </ModernTooltip>
651
+ </div>
652
+ </div>
653
+ </div>
654
+ </div>
655
+ </td>
656
+ <td className="p-2">
657
+ <span
658
+ className="rounded bg-localtower-800 text-localtower-100 border border-localtower-500 outline-none hover:border-localtower-450 hover:bg-900 active:opacity-60 transition-colors inline-flex gap-1 cursor-pointer"
659
+ style={{
660
+ opacity: formRows.length === 1 ? 0.5 : 1,
661
+ cursor: formRows.length === 1 ? "not-allowed" : "pointer",
662
+ }}
663
+ >
664
+ <X
665
+ onClick={(event) => handleDeleteRow(index, event)}
666
+ className="text-xs px-1.5 py-1.5"
667
+ />
668
+ </span>
669
+ </td>
670
+ </tr>
671
+ ))}
672
+ </tbody>
673
+ </table>
674
+ <div className="flex justify-start py-4">
675
+ <button
676
+ className="text-xs px-1.5 py-1.5 pr-2.5 rounded bg-localtower-800 text-localtower-100 border border-localtower-500 outline-none hover:border-localtower-450 hover:bg-900 active:opacity-60 transition-colors inline-flex gap-1"
677
+ onClick={handleAddRow}
678
+ >
679
+ <Plus strokeWidth={1} size={16} />
680
+ Add action
681
+ </button>
682
+ </div>
683
+ </div>
684
+ );
685
+ };
686
+
687
+ export default NewMigrationForm;