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,432 @@
1
+ import React, { useState, useEffect, CSSProperties } 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 NewModelForm = () => {
10
+ const COLUMN_DEFAULTS = window.COLUMN_DEFAULTS;
11
+
12
+ const DEFAULT_LINE = {
13
+ column_type: "string",
14
+ column_name: "",
15
+ default: "",
16
+ unique: false,
17
+ foreign_key: false,
18
+ index: "",
19
+ index_algorithm: "default",
20
+ nullable: true,
21
+ };
22
+
23
+ const [formRows, setFormRows] = useState([DEFAULT_LINE]);
24
+ const [showOptions, setShowOptions] = useState({});
25
+
26
+ const handleAddRow = (event) => {
27
+ event.preventDefault();
28
+
29
+ // Don't add if the name is empty:
30
+ if (formRows.at(-1).column_name === "") {
31
+ return;
32
+ }
33
+
34
+ setFormRows([...formRows, { ...DEFAULT_LINE }]);
35
+ };
36
+
37
+ const handleDeleteRow = (index, event) => {
38
+ event.preventDefault();
39
+
40
+ // Don't delete if there is only one row:
41
+ if (formRows.length === 1) {
42
+ return;
43
+ }
44
+
45
+ setFormRows(formRows.filter((row, rowIndex) => rowIndex !== index));
46
+ };
47
+
48
+ const handleInputChange = (index, event) => {
49
+ const { name, value } = event.target;
50
+ const updatedRows = [...formRows];
51
+
52
+ let newValue = value;
53
+
54
+ // Sanitise default input:
55
+ if (name === "column_name") {
56
+ newValue = Stores().snakeCase(newValue);
57
+ newValue = newValue.trim();
58
+ }
59
+
60
+ updatedRows[index][name] = newValue;
61
+
62
+ // Reset default if change.
63
+ if (name === "column_type") {
64
+ // result index:
65
+ // result default:
66
+ updatedRows[index].index = "";
67
+ updatedRows[index].default = "";
68
+ // close modal:
69
+ setShowOptions({ [index]: false });
70
+ }
71
+
72
+ if (name === "default" && newValue !== "") {
73
+ updatedRows[index].nullable = false;
74
+ }
75
+
76
+ // For the selector of the referenced model:
77
+ if (
78
+ name === "column_type" &&
79
+ value === "references" &&
80
+ Stores().APP_MODELS[0]
81
+ ) {
82
+ updatedRows[index].column_name = Stores().APP_MODELS[0].underscore;
83
+ updatedRows[index].foreign_key = true;
84
+ }
85
+
86
+ if (
87
+ updatedRows[index].column_type !== "references" &&
88
+ updatedRows[index].foreign_key === true
89
+ ) {
90
+ updatedRows[index].foreign_key = false;
91
+ }
92
+
93
+ // Reset unique if no index is selected:
94
+ if (name === "index" && newValue === "") {
95
+ updatedRows[index].unique = false;
96
+ updatedRows[index].index_algorithm = "default";
97
+ }
98
+
99
+ setFormRows(updatedRows);
100
+ };
101
+
102
+ const handleCheckboxChange = (index, event) => {
103
+ const { name, checked, value } = event.target;
104
+ const updatedRows = [...formRows];
105
+
106
+ let finalValue = checked;
107
+
108
+ if (name === "nullable" && checked && updatedRows[index].default !== "") {
109
+ return false;
110
+ }
111
+
112
+ if (name === "unique" && checked && updatedRows[index].index === "") {
113
+ updatedRows[index].index = "default";
114
+ }
115
+
116
+ if (
117
+ name === "index_algorithm" &&
118
+ checked &&
119
+ updatedRows[index].index != ""
120
+ ) {
121
+ finalValue = value === "concurrently" ? "concurrently" : "default";
122
+ }
123
+
124
+ updatedRows[index][name] = finalValue;
125
+
126
+ setFormRows(updatedRows);
127
+ };
128
+
129
+ const handleOptionClick = (index, option) => {
130
+ const updatedRows = [...formRows];
131
+ updatedRows[index].default = option.value;
132
+
133
+ if (updatedRows[index].default !== "") {
134
+ updatedRows[index].nullable = false;
135
+ } else {
136
+ updatedRows[index].nullable = true;
137
+ }
138
+
139
+ setFormRows(updatedRows);
140
+ setShowOptions({ [index]: false });
141
+ };
142
+
143
+ const handleShowOptions = (index, event) => {
144
+ event.preventDefault();
145
+
146
+ if (showOptions[index]) {
147
+ setShowOptions({ [index]: false });
148
+ } else {
149
+ setShowOptions({ [index]: true });
150
+ }
151
+ };
152
+
153
+ const addToForm = () => {
154
+ // Add the form to the hidden input:
155
+ document.getElementById("form_attributes").value = JSON.stringify(formRows);
156
+ };
157
+
158
+ useEffect(() => {
159
+ addToForm();
160
+ }, [formRows]); // This effect depends on formRows
161
+
162
+ return (
163
+ <div>
164
+ <table className="w-full max-w-screen-xl">
165
+ <thead className="text-xs h-8">
166
+ <tr>
167
+ <th>Name</th>
168
+ <th>Type</th>
169
+ <th>Default value</th>
170
+ <th>Index</th>
171
+ <th>Options</th>
172
+ <th></th>
173
+ </tr>
174
+ </thead>
175
+ <tbody className="border-b border-localtower-600">
176
+ {formRows.map((row, index) => (
177
+ <tr
178
+ key={`row-${index}`}
179
+ className="border-t border-localtower-600 transition-colors"
180
+ >
181
+ {row.column_type === "references" ? (
182
+ <td className="w-60 py-2 pr-3">
183
+ <div className="w-60">
184
+ <DarkSelect
185
+ options={Stores().modelsForSelect()}
186
+ defaultValue={Stores().modelsForSelect()[0]}
187
+ onChange={(selectedOption) => {
188
+ const syntheticEvent = {
189
+ target: {
190
+ name: "column_name",
191
+ value: selectedOption.value,
192
+ },
193
+ };
194
+ handleInputChange(index, syntheticEvent);
195
+ }}
196
+ />
197
+ </div>
198
+ </td>
199
+ ) : (
200
+ <td className="w-60 py-2 pr-3">
201
+ <div className="w-60">
202
+ <input
203
+ type="text"
204
+ 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"
205
+ name="column_name"
206
+ value={row.column_name}
207
+ onChange={(event) => handleInputChange(index, event)}
208
+ />
209
+ </div>
210
+ </td>
211
+ )}
212
+ <td className="w-48 py-2 pr-3">
213
+ <div className="w-48">
214
+ <DarkSelect
215
+ options={Stores().typesForSelect()}
216
+ defaultValue={Stores().typesForSelect()[0]}
217
+ onChange={(selectedOption) => {
218
+ if (selectedOption && selectedOption.value) {
219
+ const syntheticEvent = {
220
+ target: {
221
+ name: "column_type",
222
+ value: selectedOption.value,
223
+ },
224
+ };
225
+ handleInputChange(index, syntheticEvent);
226
+ }
227
+ }}
228
+ />
229
+ </div>
230
+ </td>
231
+ <td className="w-40 py-2 pr-3">
232
+ {row.column_type !== "references" && (
233
+ <div style={{ position: "relative" }} className="w-40">
234
+ <input
235
+ type="text"
236
+ name="default"
237
+ 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"
238
+ value={row.default}
239
+ placeholder="NULL"
240
+ onChange={(event) => handleInputChange(index, event)}
241
+ />
242
+ {COLUMN_DEFAULTS[row.column_type] && (
243
+ <div className="absolute inset-y-0 right-0 pl-3 pr-1 flex space-x-1 items-center">
244
+ <button
245
+ 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"
246
+ onClick={(event) => handleShowOptions(index, event)}
247
+ >
248
+ <TableOfContents
249
+ strokeWidth={1}
250
+ size={17}
251
+ className="text-localtower-200"
252
+ />
253
+ </button>
254
+ </div>
255
+ )}
256
+ {COLUMN_DEFAULTS[row.column_type] && showOptions[index] && (
257
+ <div
258
+ 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"
259
+ style={{
260
+ display: showOptions[index] ? "block" : "none",
261
+ position: "absolute",
262
+ top: "100%",
263
+ left: "0",
264
+ right: "0",
265
+ }}
266
+ >
267
+ {COLUMN_DEFAULTS[row.column_type] &&
268
+ COLUMN_DEFAULTS[row.column_type].map((option) => (
269
+ <div
270
+ key={option.value}
271
+ onClick={() => handleOptionClick(index, option)}
272
+ className="hover:bg-localtower-900 p-2 active:opacity-60 cursor-pointer flex flex-row gap-2 items-center"
273
+ >
274
+ <div className="py-1 flex justify-center items-center text-localtower-50">
275
+ {option.label}
276
+ </div>
277
+ <div className="text-localtower-400 py-1 flex justify-center items-center text-xss font-mono">
278
+ {option.example}
279
+ </div>
280
+ </div>
281
+ ))}
282
+ </div>
283
+ )}
284
+ </div>
285
+ )}
286
+ </td>
287
+ <td className="py-2 pr-3 w-40">
288
+ {row.column_type !== "references" && (
289
+ <div className="w-40">
290
+ <DarkSelect
291
+ options={Stores().indexesForSelect()}
292
+ value={row.index}
293
+ defaultValue={Stores().indexesForSelect()[0]}
294
+ customClassNamesOverrides={{ menu: "!w-40" }}
295
+ onChange={(selectedOption) => {
296
+ const syntheticEvent = {
297
+ target: {
298
+ name: "index",
299
+ value: selectedOption.value,
300
+ },
301
+ };
302
+ handleInputChange(index, syntheticEvent);
303
+ }}
304
+ />
305
+ </div>
306
+ )}
307
+ </td>
308
+ <td className="py-2 pr-3">
309
+ <div className="flex flex-row gap-1">
310
+ {row.column_type !== "references" && (
311
+ <ModernTooltip
312
+ content="Use the 'Nullable' option to allow null values."
313
+ title="Nullable"
314
+ >
315
+ <label
316
+ 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"
317
+ style={{
318
+ opacity: row.default === "" ? 1 : 0.33,
319
+ backgroundColor: row.nullable ? "black" : "",
320
+ }}
321
+ >
322
+ <input
323
+ type="checkbox"
324
+ name="nullable"
325
+ checked={row.nullable}
326
+ onChange={(event) =>
327
+ handleCheckboxChange(index, event)
328
+ }
329
+ />
330
+ <span>NULL</span>
331
+ </label>
332
+ </ModernTooltip>
333
+ )}
334
+ <ModernTooltip
335
+ content="Use the 'Foreign Key' option to create a foreign key to another table."
336
+ title="Foreign Key"
337
+ >
338
+ <label
339
+ 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"
340
+ style={{
341
+ backgroundColor: row.foreign_key ? "black" : "",
342
+ }}
343
+ >
344
+ <input
345
+ type="checkbox"
346
+ name="foreign_key"
347
+ checked={row.foreign_key}
348
+ onChange={(event) => handleCheckboxChange(index, event)}
349
+ />
350
+ <span>FK</span>
351
+ </label>
352
+ </ModernTooltip>
353
+ {row.column_type !== "references" && (
354
+ <ModernTooltip
355
+ content="Use the 'Unique' option to create a unique index."
356
+ title="Index Unique"
357
+ >
358
+ <label
359
+ 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"
360
+ style={{
361
+ backgroundColor: row.unique ? "black" : "",
362
+ }}
363
+ >
364
+ <input
365
+ type="checkbox"
366
+ name="unique"
367
+ checked={row.unique}
368
+ onChange={(event) =>
369
+ handleCheckboxChange(index, event)
370
+ }
371
+ />
372
+ <span>IU</span>
373
+ </label>
374
+ </ModernTooltip>
375
+ )}
376
+ <ModernTooltip
377
+ content="Use the 'Index Concurrently' option to create indexes without blocking writes to the table."
378
+ title="Index Concurrently"
379
+ >
380
+ <label
381
+ 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"
382
+ style={{
383
+ backgroundColor:
384
+ row.index_algorithm === "concurrently" ? "black" : "",
385
+ opacity: row.index != "" ? 1 : 0.33,
386
+ }}
387
+ >
388
+ <input
389
+ type="checkbox"
390
+ name="index_algorithm"
391
+ value="concurrently"
392
+ checked={row.index_algorithm === "concurrently"}
393
+ onChange={(event) => handleCheckboxChange(index, event)}
394
+ />
395
+ <span>IC</span>
396
+ </label>
397
+ </ModernTooltip>
398
+ </div>
399
+ </td>
400
+ <td className="p-2 flex flex-row justify-end items-center">
401
+ <span
402
+ 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"
403
+ style={{
404
+ opacity: formRows.length === 1 ? 0.5 : 1,
405
+ cursor: formRows.length === 1 ? "not-allowed" : "pointer",
406
+ }}
407
+ >
408
+ <X
409
+ onClick={(event) => handleDeleteRow(index, event)}
410
+ className="text-xs px-1.5 py-1.5"
411
+ />
412
+ </span>
413
+ </td>
414
+ </tr>
415
+ ))}
416
+ </tbody>
417
+ </table>
418
+ <div className="flex justify-start py-4">
419
+ <button
420
+ 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 disabled:opacity-60 disabled:cursor-not-allowed"
421
+ onClick={handleAddRow}
422
+ disabled={formRows.at(-1).column_name === ""}
423
+ >
424
+ <Plus strokeWidth={1} size={16} />
425
+ Add column
426
+ </button>
427
+ </div>
428
+ </div>
429
+ );
430
+ };
431
+
432
+ export default NewModelForm;