localtower 1.0.0 → 2.0.1

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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +133 -33
  3. data/app/assets/javascripts/localtower/application.js +30830 -0
  4. data/app/assets/javascripts/localtower/application.js.map +7 -0
  5. data/app/assets/stylesheets/localtower/application.css +1179 -0
  6. data/app/assets/stylesheets/src/application.css +68 -0
  7. data/app/controllers/localtower/pages_controller.rb +20 -35
  8. data/app/javascript/application.js +46 -0
  9. data/app/javascript/components/DarkSelect.js +115 -0
  10. data/app/javascript/components/ModernTooltip.js +26 -0
  11. data/app/javascript/components/NewMigrationForm.js +687 -0
  12. data/app/javascript/components/NewModelForm.js +432 -0
  13. data/app/javascript/components/data/Stores.js +294 -0
  14. data/app/views/layouts/localtower/application.html.erb +80 -66
  15. data/app/views/localtower/_migration.html.erb +30 -0
  16. data/app/views/localtower/pages/_alert_no_models.html.erb +1 -1
  17. data/app/views/localtower/pages/migrations.html.erb +24 -55
  18. data/app/views/localtower/pages/new_migration.html.erb +33 -96
  19. data/app/views/localtower/pages/new_model.html.erb +17 -83
  20. data/config/routes.rb +1 -1
  21. data/lib/localtower/engine.rb +9 -2
  22. data/lib/localtower/generators/migration.rb +218 -91
  23. data/lib/localtower/generators/model.rb +29 -20
  24. data/lib/localtower/generators/service_objects/insert_array.rb +5 -3
  25. data/lib/localtower/generators/service_objects/insert_defaults.rb +73 -3
  26. data/lib/localtower/generators/service_objects/insert_foreign_keys.rb +35 -0
  27. data/lib/localtower/generators/service_objects/insert_indexes.rb +41 -18
  28. data/lib/localtower/generators/service_objects/insert_nullable.rb +5 -3
  29. data/lib/localtower/status.rb +3 -1
  30. data/lib/localtower/tools.rb +20 -9
  31. data/lib/localtower/version.rb +1 -1
  32. data/lib/localtower.rb +0 -6
  33. data/public/com/favicon-64.png +0 -0
  34. data/public/com/twitter-cover-1.png +0 -0
  35. data/public/com/twitter-cover-2.png +0 -0
  36. data/public/fonts/Circular/CircularStd-Black.otf +0 -0
  37. data/public/fonts/Circular/CircularStd-BlackItalic.otf +0 -0
  38. data/public/fonts/Circular/CircularStd-Bold.otf +0 -0
  39. data/public/fonts/Circular/CircularStd-BoldItalic.otf +0 -0
  40. data/public/fonts/Circular/CircularStd-Book.otf +0 -0
  41. data/public/fonts/Circular/CircularStd-BookItalic.otf +0 -0
  42. data/public/fonts/Circular/CircularStd-Light Italic.otf +0 -0
  43. data/public/fonts/Circular/CircularStd-Light.otf +0 -0
  44. data/public/fonts/Circular/CircularStd-Medium.otf +0 -0
  45. data/public/fonts/Circular/CircularStd-MediumItalic.otf +0 -0
  46. data/public/fonts/Circular/Read Me.txt +5 -0
  47. data/public/fonts/Circular/www.dfonts.org.url +2 -0
  48. data/public/screenshots/v0.1.1/1_schema.png +0 -0
  49. data/public/screenshots/v0.1.1/2_models_1.png +0 -0
  50. data/public/screenshots/v0.1.1/2_models_2.png +0 -0
  51. data/public/screenshots/v0.1.1/3_relations.png +0 -0
  52. data/public/screenshots/v0.1.1/4_migrations.png +0 -0
  53. data/public/screenshots/v0.1.6/1_schema.png +0 -0
  54. data/public/screenshots/v0.1.6/2_models_1.png +0 -0
  55. data/public/screenshots/v0.1.6/3_relations.png +0 -0
  56. data/public/screenshots/v0.1.6/4_migrations.png +0 -0
  57. data/public/screenshots/v0.1.6/5_capture.png +0 -0
  58. data/public/screenshots/v1.0.0/migrations.png +0 -0
  59. data/public/screenshots/v1.0.0/models.png +0 -0
  60. data/public/screenshots/v1.0.0/new_migration.png +0 -0
  61. data/public/screenshots/v1.0.0/new_model.png +0 -0
  62. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.44.30.jpg +0 -0
  63. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.44.42.jpg +0 -0
  64. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.44.54.jpg +0 -0
  65. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.48.21.jpg +0 -0
  66. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.48.28.jpg +0 -0
  67. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.48.51.jpg +0 -0
  68. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.49.09.jpg +0 -0
  69. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.50.04.jpg +0 -0
  70. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.50.11.jpg +0 -0
  71. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.50.19.jpg +0 -0
  72. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.51.47.jpg +0 -0
  73. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.51.48.jpg +0 -0
  74. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.52.02.jpg +0 -0
  75. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.52.18.jpg +0 -0
  76. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.52.26.jpg +0 -0
  77. data/public/screenshots/v2.0.0/Screenshot 2024-11-18 at 22.52.38.jpg +0 -0
  78. data/public/screenshots/v2.0.0/migrations.png +0 -0
  79. data/public/screenshots/v2.0.0/new_migration.png +0 -0
  80. data/public/screenshots/v2.0.0/new_model.png +0 -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 +146 -104
  87. data/spec/dummy/config/database.yml +4 -6
  88. data/spec/dummy/config/puma.rb +1 -1
  89. data/spec/dummy/db/schema.rb +2 -39
  90. data/spec/dummy/lib/tasks/reset.rake +16 -0
  91. data/spec/dummy/log/development.log +22746 -15994
  92. data/spec/dummy/log/localtower.log +747 -1891
  93. data/spec/dummy/log/test.log +1145 -0
  94. data/spec/dummy/tmp/cache/sessions/localtower/726/FE1/_session_id%3A2%3A%3A69d6764784f832ea25700ea2f242210f3f8d94b48912b0f09ffc44311f0376eb +0 -0
  95. data/spec/dummy/tmp/cache/sessions/localtower/77E/121/_session_id%3A2%3A%3A0b3c1fe7154461ba023ba6c81dd1b999348757880b505f4b082faed263a627f9 +1 -0
  96. data/spec/dummy/tmp/cache/sessions/localtower/7EB/8A1/_session_id%3A2%3A%3Af88237c1d157546a5dcde894fd6c1410162a8eba318748d4ac573ee75e40df16 +0 -0
  97. data/spec/dummy/tmp/local_secret.txt +1 -0
  98. data/spec/factories/migration.rb +77 -40
  99. data/spec/factories/model.rb +46 -26
  100. data/spec/lib/localtower/generators/migration_spec.rb +16 -24
  101. data/spec/lib/localtower/generators/model_spec.rb +71 -8
  102. data/spec/lib/localtower/generators/service_objects/insert_array_spec.rb +1 -1
  103. data/spec/lib/localtower/generators/service_objects/insert_defaults_spec.rb +1 -1
  104. data/spec/lib/localtower/generators/service_objects/insert_foreign_keys_spec.rb +101 -0
  105. data/spec/lib/localtower/generators/service_objects/insert_indexes_spec.rb +1 -1
  106. data/spec/lib/localtower/generators/service_objects/insert_nullable_spec.rb +2 -2
  107. data/spec/spec_helper.rb +2 -3
  108. metadata +107 -238
  109. data/app/views/localtower/pages/models.html.erb +0 -63
  110. data/public/css/app.css +0 -55
  111. data/public/js/app.js +0 -337
  112. data/public/light-bootstrap-dashboard-master/assets/css/animate.min.css +0 -6
  113. data/public/light-bootstrap-dashboard-master/assets/css/bootstrap.min.css +0 -5
  114. data/public/light-bootstrap-dashboard-master/assets/css/demo.css +0 -61
  115. data/public/light-bootstrap-dashboard-master/assets/css/light-bootstrap-dashboard.css +0 -2789
  116. data/public/light-bootstrap-dashboard-master/assets/css/pe-icon-7-stroke.css +0 -632
  117. data/public/light-bootstrap-dashboard-master/assets/fonts/Pe-icon-7-stroke.eot +0 -0
  118. data/public/light-bootstrap-dashboard-master/assets/fonts/Pe-icon-7-stroke.svg +0 -212
  119. data/public/light-bootstrap-dashboard-master/assets/fonts/Pe-icon-7-stroke.ttf +0 -0
  120. data/public/light-bootstrap-dashboard-master/assets/fonts/Pe-icon-7-stroke.woff +0 -0
  121. data/public/light-bootstrap-dashboard-master/assets/img/default-avatar.png +0 -0
  122. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-0.jpg +0 -0
  123. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-1.jpg +0 -0
  124. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-2.jpg +0 -0
  125. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-3.jpg +0 -0
  126. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-4.jpg +0 -0
  127. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-5.jpg +0 -0
  128. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-6.jpg +0 -0
  129. data/public/light-bootstrap-dashboard-master/assets/img/faces/face-7.jpg +0 -0
  130. data/public/light-bootstrap-dashboard-master/assets/img/faces/tim_vector.jpe +0 -0
  131. data/public/light-bootstrap-dashboard-master/assets/img/favicon.ico +0 -0
  132. data/public/light-bootstrap-dashboard-master/assets/img/loading-bubbles.svg +0 -14
  133. data/public/light-bootstrap-dashboard-master/assets/img/mask.png +0 -0
  134. data/public/light-bootstrap-dashboard-master/assets/img/new_logo.png +0 -0
  135. data/public/light-bootstrap-dashboard-master/assets/img/sidebar-1.jpg +0 -0
  136. data/public/light-bootstrap-dashboard-master/assets/img/sidebar-2.jpg +0 -0
  137. data/public/light-bootstrap-dashboard-master/assets/img/sidebar-3.jpg +0 -0
  138. data/public/light-bootstrap-dashboard-master/assets/img/sidebar-4.jpg +0 -0
  139. data/public/light-bootstrap-dashboard-master/assets/img/sidebar-5.jpg +0 -0
  140. data/public/light-bootstrap-dashboard-master/assets/img/tim_80x80.png +0 -0
  141. data/public/light-bootstrap-dashboard-master/assets/js/bootstrap-checkbox-radio-switch.js +0 -502
  142. data/public/light-bootstrap-dashboard-master/assets/js/bootstrap-notify.js +0 -404
  143. data/public/light-bootstrap-dashboard-master/assets/js/bootstrap-select.js +0 -438
  144. data/public/light-bootstrap-dashboard-master/assets/js/bootstrap.min.js +0 -7
  145. data/public/light-bootstrap-dashboard-master/assets/js/chartist.min.js +0 -9
  146. data/public/light-bootstrap-dashboard-master/assets/js/demo.js +0 -152
  147. data/public/light-bootstrap-dashboard-master/assets/js/jquery-1.10.2.js +0 -9789
  148. data/public/light-bootstrap-dashboard-master/assets/js/light-bootstrap-dashboard.js +0 -179
  149. data/public/logo-localtower-white-300.png +0 -0
  150. data/public/logo-localtower-white.png +0 -0
  151. data/public/logo-localtower.png +0 -0
  152. data/public/vendor/font-awesome.min.css +0 -4
  153. data/public/vendor/highlight-js-default.min.css +0 -9
  154. data/spec/dummy/app/models/post.rb +0 -3
  155. data/spec/dummy/app/models/user.rb +0 -3
  156. data/spec/dummy/db/migrate/20230119221452_create_users.rb +0 -14
  157. data/spec/dummy/db/migrate/20230119221751_change_users_at1674166670.rb +0 -7
  158. data/spec/dummy/db/migrate/20230119222054_create_posts.rb +0 -11
  159. data/spec/dummy/db/migrate/20230119222106_change_posts_at1674166865.rb +0 -5
  160. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/-Y/-YOiiBKqc2UODHFjctm8xc7xFoZaL7zOjWQj6qQ2wyE.cache +0 -1
  161. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/-d/-dwueM4vmPt8L51S3jeSyg_AjGDcj0GUN6pDpCA1gCg.cache +0 -3
  162. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/0g/0gaJnJQdtd2ACbihXxn8OnjLWlDjnQ_WxfgOpbiLzhg.cache +0 -1
  163. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/0r/0rFCsCV9kZnEYtZZ6sfig8329OU31bqjecDFqSVank8.cache +0 -1
  164. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/1J/1J2k_CpnQE3d-PZAQwOVGQALGkta4qVvhdsKjgG0e4Q.cache +0 -0
  165. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/2p/2pYVH2Z_syqh6ok8QYxJNKxXpx1Iwppf6JGElZI0gpw.cache +0 -0
  166. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/3W/3WBmqd-2V6q5N-jvbyp-tlcfn3aHYMwBppbOUm7x4qg.cache +0 -1
  167. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/4K/4KFPlHkhdDW0riGUmlbaR-kmDz6JUnQvY6fwW8qsdaE.cache +0 -3
  168. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/6x/6xMeRWmLpNK_flx6-JA3KazvUxSCxyPxHv9Zm3eC26Q.cache +0 -1
  169. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/7d/7dNqI_dCDJLJmI1oM4xwFp9nRRNOem-4P4OD7PMyz2E.cache +0 -0
  170. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/8X/8XquhxVcp5A8QquLtxO8NgKTMJch0eqQmzFmRGIZP6I.cache +0 -1
  171. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Ah/AhdfXXtU63kXS4TnsH2Vi1yWgVkIEeV8Z8XA68hrNQc.cache +0 -0
  172. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Bt/BtAePnwLSGw82xUGI7wuhWDfuwarOQVS91YqCsweMcs.cache +0 -0
  173. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Bx/BxzoG0KxeNLac4xTNeJv3qfeytbNBw58xj2zD-xdbrE.cache +0 -2
  174. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/CC/CCD0ROPX0yxHwNpVZmuP8ZNtgQpaVCMXpdzC0Wb5n24.cache +0 -1
  175. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/DS/DS43oxBg6K5PMWLn2mTy_4EnxI03ehHkxpjV7NCi8yM.cache +0 -1
  176. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Do/Do83AoOat5W-c1g7piDGy2GMmffsY6JY0Qfuh5PY3GM.cache +0 -2
  177. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/EI/EI0Nxk-VRATWstMuVCVc0_GdYlQ-a0dx6n2g0l3vIik.cache +0 -0
  178. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/EN/ENhJnzJAU2IK-7aHqubj9N8Jo_UTmjG_VEQqyIkolQ8.cache +0 -1
  179. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Fc/FcS6VUHN3Bd4pHGqd5NHA8jA4OLwWrU94s3b4GGxX9c.cache +0 -1
  180. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/G0/G0kukI-r0q0Vbrg6e_jnYJoYcOX8K__h-mwKbCf4twE.cache +0 -0
  181. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/I1/I1Jw08-mz8xzgrgi6giCzpf1UmzGTSbl4eJEw4DAoJM.cache +0 -1
  182. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/IV/IVOO1dXBmgjieDk__g57p6aYt0Z3CmfTa32jhegoyko.cache +0 -3
  183. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/JY/JYPmv5WP4wxdI9EKEEPkK_fVtqeSLfmint_5E8fQLQ8.cache +0 -1
  184. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/MX/MXI3KxaQPZGImSvCNZ_TbQVruWCJ3E0xiVxza1ZCAjM.cache +0 -1
  185. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/OU/OUoioCqXALK909jXPV3VSyCJIdNC7bsogUfdnRTpc5o.cache +0 -1
  186. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/PH/PHjtqNLUPAUDiSlu5AbPjlIo20mOGNm0uNjMLhX2NvM.cache +0 -0
  187. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/RC/RC9vfALY5K634pTeau0BAhTHl7d5_5yA3tM-QTLiKtI.cache +0 -0
  188. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/RG/RG1PrmlixwaUlG8BV0kcm_3F7OQekxsrLYjBdf403-k.cache +0 -2
  189. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Ut/UtoMO6n6FHTpRCGk9VfxlfTI2Ao2GYJ_6kMzx3B9VH8.cache +0 -1
  190. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/VE/VExnbpDk3LxYdPk3htUrZQXPI8NK_zlKtSFfVXJlxU4.cache +0 -1
  191. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Wm/WmKTjykiU-Tx_Faf5zduEeEQ-DozAs5omKToM2l1cZM.cache +0 -0
  192. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/X-/X-KjhDPd0nI155N_FaxFSgaOiYo0_ytqlmkBlLM7ayw.cache +0 -1
  193. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/XX/XXVhj-O91tJ5c8pz7DQlTABOv8TIRVg5haw9VOq9HuI.cache +0 -1
  194. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/YC/YCiifo5NCMwDChFqYFiV4EaYEx8hy3Efle1PsGbIdak.cache +0 -1
  195. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Ya/YayMISAqD-Y3vBFywwKrXbUovGf5o77HUF5s8mnQgO0.cache +0 -0
  196. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Zl/Zl-NEb0aDLmZ9gN4gdY1OYlbUXD4JhkFY_y23m55zmo.cache +0 -0
  197. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/_h/_hnjPptBeD3GP5B0iJR6pvyVkiMiq-o2TxaYmGf9KJo.cache +0 -3
  198. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/_p/_poEF6QbTPYJiQCFGoXxIXjmvcn6T0I87ElzJGHguk0.cache +0 -1
  199. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/aa/aaSj2zt8ddc87nZqTpOI9gRCKVnoalXKFBww7t4t3Kg.cache +0 -0
  200. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/bZ/bZHWUkrOLp0WM_Ogo36Qjt4cxDt-rOFgtcj4LNB-BVE.cache +0 -1
  201. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/cP/cPSNG_663TNT944Rke919luRS6FtpMwVc_7aDph645w.cache +0 -1
  202. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/ce/ceq8fcZGIoqWrCl9vrCRrywEdx42iy9PrGG9CwFSIF4.cache +0 -0
  203. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/cl/cljhP0Jc018nLnG1rpdg-FUf6mlIqrQYvXTNXTNG9Bw.cache +0 -0
  204. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/dm/dmod3fTZdoKso2FGM5RTQtb4kTB6vywsiXLWDygYFN4.cache +0 -1
  205. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/dy/dySDGcUTqlKx2MTvOmIEP-WzhyhCfU5xhN9qgxbz3rI.cache +0 -0
  206. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/gQ/gQYdmEzbLjTcFCnsfzXzEmeminOchF263snAk3IvXM0.cache +0 -1
  207. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/gh/ghvuMAqccBGljlmEaI3Gt5tH5rEg6hseKYMYfRMkHdY.cache +0 -1
  208. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/gt/gt8QC7k92A4hXA3HlCpQulW73tqd6x63I3uT-YrGMj0.cache +0 -1
  209. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/hf/hfVg-y7dPSQKbb7V3I7M5w7IpOhDiIB2H31d2tmbZMU.cache +0 -0
  210. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/iF/iFLwyhZIu1Jxm77NUT2qWeTDMmyELW4U85t9rGE_KVg.cache +0 -0
  211. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/kF/kFlIcBBnSyKE00OjpM90pqSn93mLMUv9esz86nrO-Cs.cache +0 -1
  212. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/ma/MazNt_5OaYexRuZnccZZ_7AONlxn1a4W3KdWyHyBdws.cache +0 -0
  213. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/ma/ma3w08gnDTeqwY2-C9BlToxA6-AS8bvXlu-nBiz0UYs.cache +0 -1
  214. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/n_/n_xYqQYhwEMQknb3jFQnjlxxBE9TzMNHCdJ-bEyZFIw.cache +0 -2
  215. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/nx/nxTv3sKVUQZADJyM3dPaVmUA78MIsMLD_K279yN_GsI.cache +0 -2
  216. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/pv/pv6tV3CDkIAmLXdU8EPLlNEkXAKJPufVD4VP30o4fWo.cache +0 -0
  217. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/qP/qPmv5snMrDw830S6hSICDcnIy7kVEWoFKXhGKT38lG4.cache +0 -2
  218. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/sY/sYvTb1uPWWmnHJNXKuW_xSco-aUb-rN2f0J35zSIzuc.cache +0 -1
  219. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/su/suQ_jjDxRiIn4VEqFJYKaBWJILaeGKBvoTv49XNX0vo.cache +0 -1
  220. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/u8/u8IrW5IIQ7espwk2Vpk23zheL9YZA0tnbKq9X7E-WA0.cache +0 -1
  221. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/wh/whzETExjcZCkn4msasD2aylgfhfpKPZrNxTkY-SOIDg.cache +0 -2
  222. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/x7/x7PYh8DJvPykcEqpVab2vcY9-GFz-3cqtoMlRAu94Uc.cache +0 -2
  223. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/y6/y6oxsJD3pAY9ph1_5M-77uiEjTVw8BheLZNygCKPm1g.cache +0 -1
  224. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/yH/yHZ-a1J23ZCf2n5mEHINMz23Iec2cLGTKauW7k4yGTE.cache +0 -0
  225. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/z7/z7KfUWYoSaGq8tQ5mbHwyfXyT3Pa4FWKJswHcIdJuds.cache +0 -1
  226. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/zu/zuExWc5WHxeOUPZUKHl9a9ZRmN50g7jMOD28macn6M0.cache +0 -0
  227. data/spec/dummy/tmp/pids/server.pid +0 -1
@@ -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;