databasium 0.1.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 (125) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +32 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +109 -0
  5. data/Rakefile +6 -0
  6. data/app/assets/builds/application.js +9045 -0
  7. data/app/assets/builds/application.js.map +7 -0
  8. data/app/assets/builds/databasium.css +2 -0
  9. data/app/assets/config/databasium_manifest.js +1 -0
  10. data/app/assets/javascript/databasium/application.js +2 -0
  11. data/app/assets/javascript/databasium/controllers/attribute_controller.js +27 -0
  12. data/app/assets/javascript/databasium/controllers/collapse_controller.js +18 -0
  13. data/app/assets/javascript/databasium/controllers/error_controller.js +15 -0
  14. data/app/assets/javascript/databasium/controllers/filter_controller.js +224 -0
  15. data/app/assets/javascript/databasium/controllers/flash_controller.js +18 -0
  16. data/app/assets/javascript/databasium/controllers/graph_controller.js +193 -0
  17. data/app/assets/javascript/databasium/controllers/index.js +7 -0
  18. data/app/assets/javascript/databasium/controllers/layout_controller.js +13 -0
  19. data/app/assets/javascript/databasium/controllers/model_controller.js +32 -0
  20. data/app/assets/javascript/databasium/controllers/new_migration_controller.js +107 -0
  21. data/app/assets/javascript/databasium/controllers/relation_controller.js +10 -0
  22. data/app/assets/javascript/databasium/controllers/search_controller.js +23 -0
  23. data/app/assets/javascript/databasium/controllers/table_controller.js +283 -0
  24. data/app/assets/javascript/databasium/controllers/table_select_controller.js +19 -0
  25. data/app/assets/javascript/databasium/controllers/toggle_controller.js +28 -0
  26. data/app/assets/javascript/databasium/controllers/validation_controller.js +78 -0
  27. data/app/assets/javascript/databasium/shapes/erd_table_shape.js +54 -0
  28. data/app/assets/stylesheets/databasium/application.css +15 -0
  29. data/app/assets/stylesheets/databasium/colors.css +55 -0
  30. data/app/assets/stylesheets/databasium/custom.css +36 -0
  31. data/app/assets/stylesheets/databasium/databasium_engine.css +6 -0
  32. data/app/assets/stylesheets/databasium/pagy-tailwind.css +66 -0
  33. data/app/components/base.rb +50 -0
  34. data/app/components/databasium/collapsable.rb +62 -0
  35. data/app/components/databasium/forms/model.rb +147 -0
  36. data/app/components/databasium/forms/search.rb +31 -0
  37. data/app/components/databasium/global/error.rb +60 -0
  38. data/app/components/databasium/global/flash.rb +73 -0
  39. data/app/components/databasium/global/header_actions.rb +36 -0
  40. data/app/components/databasium/global/sidebar.rb +45 -0
  41. data/app/components/databasium/global/suggestion.rb +25 -0
  42. data/app/components/databasium/migrations/action.rb +39 -0
  43. data/app/components/databasium/migrations/file.rb +58 -0
  44. data/app/components/databasium/migrations/form.rb +222 -0
  45. data/app/components/databasium/migrations/header_actions.rb +87 -0
  46. data/app/components/databasium/migrations/migration_status.rb +22 -0
  47. data/app/components/databasium/migrations/preview.rb +29 -0
  48. data/app/components/databasium/migrations/show_turbo_stream.rb +19 -0
  49. data/app/components/databasium/migrations/sidebar.rb +28 -0
  50. data/app/components/databasium/models/attributes.rb +49 -0
  51. data/app/components/databasium/models/form.rb +100 -0
  52. data/app/components/databasium/models/header_actions.rb +51 -0
  53. data/app/components/databasium/models/model_preview.rb +31 -0
  54. data/app/components/databasium/models/sidebar.rb +25 -0
  55. data/app/components/databasium/models/templates/attribute.rb +99 -0
  56. data/app/components/databasium/models/templates/base.rb +6 -0
  57. data/app/components/databasium/models/templates/relation.rb +56 -0
  58. data/app/components/databasium/models/templates/validation.rb +285 -0
  59. data/app/components/databasium/navigation/base_icon.rb +32 -0
  60. data/app/components/databasium/navigation/frontend_icon.rb +17 -0
  61. data/app/components/databasium/navigation/get_icon.rb +26 -0
  62. data/app/components/databasium/navigation/icon.rb +28 -0
  63. data/app/components/databasium/navigation/icon_panel.rb +26 -0
  64. data/app/components/databasium/navigation/post_icon.rb +25 -0
  65. data/app/components/databasium/navigation/put_icon.rb +18 -0
  66. data/app/components/databasium/records/filter.rb +73 -0
  67. data/app/components/databasium/records/foreign_records.rb +84 -0
  68. data/app/components/databasium/records/header_actions.rb +110 -0
  69. data/app/components/databasium/records/show_turbo_stream.rb +75 -0
  70. data/app/components/databasium/records/sidebar.rb +23 -0
  71. data/app/components/databasium/records/table/record_panel.rb +60 -0
  72. data/app/components/databasium/records/table/row.rb +104 -0
  73. data/app/components/databasium/records/table.rb +125 -0
  74. data/app/components/databasium/records/table_turbo_frame.rb +37 -0
  75. data/app/components/databasium/records/utilities.rb +25 -0
  76. data/app/components/databasium/schemas/header_actions.rb +99 -0
  77. data/app/components/databasium/schemas/sidebar.rb +25 -0
  78. data/app/components/databasium/search_results/migrations.rb +37 -0
  79. data/app/components/databasium/search_results/models.rb +36 -0
  80. data/app/components/databasium/search_results/schema_models.rb +37 -0
  81. data/app/components/databasium/search_results/tables.rb +31 -0
  82. data/app/components/databasium/type_select.rb +35 -0
  83. data/app/controllers/databasium/application_controller.rb +68 -0
  84. data/app/controllers/databasium/homepage_controller.rb +5 -0
  85. data/app/controllers/databasium/migrations_controller.rb +186 -0
  86. data/app/controllers/databasium/models_controller.rb +105 -0
  87. data/app/controllers/databasium/records_controller.rb +156 -0
  88. data/app/controllers/databasium/schemas_controller.rb +52 -0
  89. data/app/helpers/databasium/application_helper.rb +4 -0
  90. data/app/helpers/databasium/heroicon_helper.rb +21 -0
  91. data/app/helpers/databasium/models_helper.rb +4 -0
  92. data/app/jobs/databasium/application_job.rb +4 -0
  93. data/app/mailers/databasium/application_mailer.rb +6 -0
  94. data/app/models/databasium/application_record.rb +5 -0
  95. data/app/models/model.json +0 -0
  96. data/app/services/databasium/migration.rb +176 -0
  97. data/app/services/databasium/model.rb +182 -0
  98. data/app/services/databasium/record.rb +65 -0
  99. data/app/services/databasium/schema.rb +146 -0
  100. data/app/views/base.rb +13 -0
  101. data/app/views/databasium/errors/non_development.rb +21 -0
  102. data/app/views/databasium/homepage/index.rb +29 -0
  103. data/app/views/databasium/migrations/index.rb +33 -0
  104. data/app/views/databasium/migrations/new.rb +29 -0
  105. data/app/views/databasium/models/index.rb +31 -0
  106. data/app/views/databasium/models/new.rb +37 -0
  107. data/app/views/databasium/records/index.rb +24 -0
  108. data/app/views/databasium/schemas/index.rb +39 -0
  109. data/app/views/layouts/databasium/application.rb +56 -0
  110. data/config/importmap.rb +12 -0
  111. data/config/initializers/heroicon.rb +12 -0
  112. data/config/initializers/pagy.rb +48 -0
  113. data/config/initializers/phlex.rb +19 -0
  114. data/config/routes.rb +31 -0
  115. data/config/tailwind.config.js +10 -0
  116. data/lib/databasium/engine.rb +57 -0
  117. data/lib/databasium/engine_mount.rb +37 -0
  118. data/lib/databasium/middleware/conditional_check_pending.rb +27 -0
  119. data/lib/databasium/templates/create_table_migration.rb.tt +29 -0
  120. data/lib/databasium/templates/migration.rb.tt +48 -0
  121. data/lib/databasium/templates/model.rb.tt +23 -0
  122. data/lib/databasium/version.rb +3 -0
  123. data/lib/databasium.rb +11 -0
  124. data/lib/tasks/databasium_tasks.rake +4 -0
  125. metadata +272 -0
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-yellow-400:oklch(85.2% .199 91.936);--color-green-100:oklch(96.2% .044 156.743);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-700:oklch(52.7% .154 150.069);--color-blue-500:oklch(62.3% .214 259.815);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-gray-300:oklch(87.2% .01 258.338);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-light:300;--font-weight-semibold:600;--font-weight-bold:700;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-background:var(--databasium-background);--color-main-text:var(--databasium-text);--color-border:var(--databasium-border);--color-panel:var(--databasium-panel)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}input:-webkit-autofill{transition:background-color 5000s ease-in-out;-webkit-box-shadow:0 0 0px 1000px var(--color-background)inset!important;-webkit-text-fill-color:var(--color-main-text)!important}input:-webkit-autofill:hover{transition:background-color 5000s ease-in-out;-webkit-box-shadow:0 0 0px 1000px var(--color-background)inset!important;-webkit-text-fill-color:var(--color-main-text)!important}input:-webkit-autofill:focus{transition:background-color 5000s ease-in-out;-webkit-box-shadow:0 0 0px 1000px var(--color-background)inset!important;-webkit-text-fill-color:var(--color-main-text)!important}}@layer components{.pagy{--B:1;--H:360;--S:48.71;--L:73.56;--A:1;--spacing:.1875rem;--padding:.5rem;--rounding:.75rem;--border-width:.0625rem;--font-size:.875rem;--font-weight:300;--line-height:1.75;--text:hsl(var(--H)var(--S)calc(var(--L) - (30*var(--B))));--text-hover:hsl(var(--H)var(--S)calc(var(--L) - (33*var(--B))));--text-current:hsl(var(--H)var(--S)calc(100*(var(--B) + 1)));--background:hsl(var(--H)var(--S)calc(var(--L) + (30*var(--B))));--background-hover:hsl(var(--H)var(--S)calc(var(--L) + (20*var(--B))));--background-current:hsl(var(--H)var(--S)var(--L));--background-input:hsl(var(--H)var(--S)calc(var(--L) + (45*var(--B))));--opacity:1;column-gap:var(--spacing);font-size:var(--font-size);--tw-leading:var(--line-height);line-height:var(--line-height);--tw-font-weight:var(--font-weight);font-weight:var(--font-weight);color:var(--text);display:flex}.pagy a:not([role=separator]){border-radius:var(--rounding);border-style:var(--tw-border-style);border-width:var(--border-width);--tw-border-style:solid;border-style:solid;border-color:var(--background-current);background-color:var(--background);padding-inline:var(--padding);padding-block:calc(var(--padding)/3);opacity:var(--opacity);display:block}.pagy a[href]:hover{background-color:var(--background-hover);color:var(--text-hover)}.pagy a:not([href]){cursor:default}.pagy a[role=link]:not([aria-current]){opacity:calc(var(--opacity)*.6)}.pagy a[aria-current]{background-color:var(--background-current);color:var(--text-current)}.pagy label{border-radius:var(--rounding);border-style:var(--tw-border-style);border-width:var(--border-width);--tw-border-style:solid;border-style:solid;border-color:var(--background-current);background-color:var(--background);padding-inline:var(--padding);padding-block:calc(var(--padding)/3 - var(--border-width));white-space:nowrap;display:inline-block}.pagy label input{border-radius:calc(var(--rounding)/2);border-style:var(--tw-border-style);border-width:var(--border-width);border-color:var(--background-current);background-color:var(--background-input);font-size:var(--font-size);--tw-leading:var(--line-height);line-height:var(--line-height);--tw-font-weight:var(--font-weight);font-weight:var(--font-weight);color:var(--text)}.scrollbar-thin::-webkit-scrollbar{border-radius:3.40282e38px;width:6px;height:6px}.scrollbar-thin::-webkit-scrollbar-track{background-color:var(--databasium-background)}.scrollbar-thin::-webkit-scrollbar-thumb{background-color:var(--databasium-panel)}.scrollbar-thin::-webkit-scrollbar-thumb:hover{background-color:var(--databasium-hover)}.scrollbar-thin{scrollbar-width:thin;scrollbar-color:var(--databasium-panel)var(--databasium-background)}}@layer utilities{.collapse{visibility:collapse}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing)*0)}.-top-1\.5{top:calc(var(--spacing)*-1.5)}.-top-2\.5{top:calc(var(--spacing)*-2.5)}.-top-3{top:calc(var(--spacing)*-3)}.-top-4{top:calc(var(--spacing)*-4)}.top-1{top:calc(var(--spacing)*1)}.top-2{top:calc(var(--spacing)*2)}.top-20{top:calc(var(--spacing)*20)}.-right-\[0\.5rem\]{right:-.5rem}.right-0{right:calc(var(--spacing)*0)}.right-4{right:calc(var(--spacing)*4)}.-bottom-5\.5{bottom:calc(var(--spacing)*-5.5)}.bottom-full{bottom:100%}.left-0{left:calc(var(--spacing)*0)}.left-1\/2{left:50%}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.z-\[9999\]{z-index:9999}.col-span-2{grid-column:span 2/span 2}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-2{margin:calc(var(--spacing)*2)}.m-4{margin:calc(var(--spacing)*4)}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing)*2)}.ms-1{margin-inline-start:calc(var(--spacing)*1)}.ms-2{margin-inline-start:calc(var(--spacing)*2)}.ms-auto{margin-inline-start:auto}.me-2{margin-inline-end:calc(var(--spacing)*2)}.me-auto{margin-inline-end:auto}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.ml-2{margin-left:calc(var(--spacing)*2)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-2{height:calc(var(--spacing)*2)}.h-3{height:calc(var(--spacing)*3)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-11{height:calc(var(--spacing)*11)}.h-15{height:calc(var(--spacing)*15)}.h-\[90dvh\]{height:90dvh}.h-dvh{height:100dvh}.h-fit{height:fit-content}.h-full{height:100%}.max-h-50{max-height:calc(var(--spacing)*50)}.max-h-64{max-height:calc(var(--spacing)*64)}.max-h-100{max-height:calc(var(--spacing)*100)}.max-h-\[calc\(100dvh-135px\)\]{max-height:calc(100dvh - 135px)}.max-h-fit{max-height:fit-content}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-\[calc\(100dvh-51px\)\]{min-height:calc(100dvh - 51px)}.min-h-full{min-height:100%}.w-1\/2{width:50%}.w-1\/3{width:33.3333%}.w-3{width:calc(var(--spacing)*3)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-10{width:calc(var(--spacing)*10)}.w-35{width:calc(var(--spacing)*35)}.w-55{width:calc(var(--spacing)*55)}.w-64{width:calc(var(--spacing)*64)}.w-80{width:calc(var(--spacing)*80)}.w-125{width:calc(var(--spacing)*125)}.w-fit{width:fit-content}.w-full{width:100%}.max-w-15{max-width:calc(var(--spacing)*15)}.max-w-40{max-width:calc(var(--spacing)*40)}.max-w-55{max-width:calc(var(--spacing)*55)}.max-w-125{max-width:calc(var(--spacing)*125)}.max-w-\[50vw\]{max-width:50vw}.max-w-fit{max-width:fit-content}.max-w-full{max-width:100%}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-fit{min-width:fit-content}.min-w-max{min-width:max-content}.flex-1{flex:1}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-x-1\/2{--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.rotate-45{rotate:45deg}.rotate-180{rotate:180deg}.animate-pulse{animation:var(--animate-pulse)}.cursor-pointer{cursor:pointer}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[max-content_1fr\]{grid-template-columns:max-content 1fr}.flex-col{flex-direction:column}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-start{justify-content:flex-start}.justify-items-center{justify-items:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-4{gap:calc(var(--spacing)*4)}.gap-5{gap:calc(var(--spacing)*5)}.gap-x-2{column-gap:calc(var(--spacing)*2)}:where(.divide-x>:not(:last-child)),:where(.divide-x-1>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-border>:not(:last-child)){border-color:var(--databasium-border)}.self-center{align-self:center}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-y-hidden{overflow-y:hidden}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-b-xl{border-bottom-right-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl)}.rounded-bl-xl{border-bottom-left-radius:var(--radius-xl)}.border,.border-1{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-r-2{border-right-style:var(--tw-border-style);border-right-width:2px}.border-b,.border-b-1{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l,.border-l-1{border-left-style:var(--tw-border-style);border-left-width:1px}.border-accent{border-color:var(--databasium-accent)}.border-blue-500{border-color:var(--color-blue-500)}.border-border{border-color:var(--databasium-border)}.border-gray-300{border-color:var(--color-gray-300)}.border-green-400{border-color:var(--color-green-400)}.border-red-400{border-color:var(--color-red-400)}.border-r-border{border-right-color:var(--databasium-border)}.border-b-border{border-bottom-color:var(--databasium-border)}.border-b-gray-300{border-bottom-color:var(--color-gray-300)}.bg-accent{background-color:var(--databasium-accent)}.bg-background{background-color:var(--databasium-background)}.bg-black\/40{background-color:#0006}@supports (color:color-mix(in lab, red, red)){.bg-black\/40{background-color:color-mix(in oklab,var(--color-black)40%,transparent)}}.bg-blue-500{background-color:var(--color-blue-500)}.bg-green-100{background-color:var(--color-green-100)}.bg-panel{background-color:var(--databasium-panel)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-white{background-color:var(--color-white)}.p-0\.5{padding:calc(var(--spacing)*.5)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.py-1{padding-block:calc(var(--spacing)*1)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.ps-1{padding-inline-start:calc(var(--spacing)*1)}.ps-4{padding-inline-start:calc(var(--spacing)*4)}.pe-2{padding-inline-end:calc(var(--spacing)*2)}.pe-4{padding-inline-end:calc(var(--spacing)*4)}.pr-10{padding-right:calc(var(--spacing)*10)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pb-6{padding-bottom:calc(var(--spacing)*6)}.pl-2{padding-left:calc(var(--spacing)*2)}.text-center{text-align:center}.text-end{text-align:end}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-5{--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-nowrap{text-wrap:nowrap}.text-wrap{text-wrap:wrap}.break-all{word-break:break-all}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent{color:var(--databasium-accent)}.text-blue-500{color:var(--color-blue-500)}.text-gray-300{color:var(--color-gray-300)}.text-green-500{color:var(--color-green-500)}.text-green-700{color:var(--color-green-700)}.text-main-text{color:var(--databasium-text)}.text-red-500{color:var(--color-red-500)}.text-red-700{color:var(--color-red-700)}.text-selected{color:var(--databasium-selected)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.underline{text-decoration-line:underline}.opacity-0{opacity:0}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-accent{--tw-shadow:var(--databasium-shadow);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-1000{--tw-duration:1s;transition-duration:1s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.hover\:cursor-pointer:hover{cursor:pointer}.hover\:border-hover:hover{border-color:var(--databasium-hover)}.hover\:bg-background:hover{background-color:var(--databasium-background)}.hover\:bg-panel:hover{background-color:var(--databasium-panel)}.hover\:text-hover:hover{color:var(--databasium-hover)}.hover\:text-red-800:hover{color:var(--color-red-800)}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}@media (min-width:64rem){.lg\:w-4xl{width:var(--container-4xl)}}}:root{--databasium-primary:var(--color-blue-500);--databasium-secondary:var(--color-blue-500);--databasium-accent:var(--color-blue-500);--databasium-background:var(--color-slate-900);--databasium-text:var(--color-slate-100);--databasium-border:var(--color-slate-600);--databasium-panel:var(--color-slate-800);--databasium-hover:var(--color-yellow-400);--databasium-selected:var(--color-green-500);--databasium-shadow:0 0 6px #60a5fa80;--databasium-active:var(--color-blue-500);--databasium-focus:var(--color-blue-500);--databasium-disabled:var(--color-blue-500);--databasium-error:var(--color-blue-500);--databasium-success:var(--color-blue-500);--databasium-warning:var(--color-blue-500)}[data-theme=white]{--app-primary:#fff;--app-secondary:yellow;--app-accent:#888;--app-background:#111;--app-text:#fff}[data-theme=ocean]{--app-primary:#0f172a;--app-secondary:#38bdf8;--app-accent:#06b6d4;--app-background:#ecfeff;--app-text:#082f49}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}
@@ -0,0 +1 @@
1
+ //= link_tree ../builds .css
@@ -0,0 +1,2 @@
1
+ import "@hotwired/turbo-rails";
2
+ import "databasium/controllers";
@@ -0,0 +1,27 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="attribute"
4
+ export default class extends Controller {
5
+ static targets = ["name", "nameInput", "nameValidationInput"];
6
+
7
+ connect() {}
8
+
9
+ updateName() {
10
+ const name = this.nameInputTarget.value;
11
+ this.nameTarget.textContent = name;
12
+ this.nameValidationInputTargets.forEach((target) => {
13
+ target.value = name;
14
+ });
15
+ }
16
+
17
+ updateValidationName(e) {
18
+ const name = this.nameInputTarget.value;
19
+ this.nameValidationInputTargets.forEach((target) => {
20
+ target.value = name;
21
+ });
22
+ }
23
+
24
+ remove() {
25
+ this.element.remove();
26
+ }
27
+ }
@@ -0,0 +1,18 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="collapse"
4
+ export default class extends Controller {
5
+ static targets = ["content", "collapseIcon"];
6
+
7
+ connect() {}
8
+
9
+ toggle(e) {
10
+ e.preventDefault();
11
+ if (this.hasContentTarget) {
12
+ this.contentTargets.forEach((target) => {
13
+ target.classList.toggle("hidden");
14
+ });
15
+ }
16
+ this.collapseIconTarget.classList.toggle("rotate-180");
17
+ }
18
+ }
@@ -0,0 +1,15 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="error"
4
+ export default class extends Controller {
5
+ static targets = [];
6
+
7
+ connect() {}
8
+
9
+ close() {
10
+ this.element.classList.add("opacity-0");
11
+ setTimeout(() => {
12
+ this.element.classList.add("hidden");
13
+ }, 1000);
14
+ }
15
+ }
@@ -0,0 +1,224 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ const OPERATOR_LABELS = {
4
+ eq: "=",
5
+ not_eq: "!=",
6
+ matches: "like",
7
+ does_not_match: "not like",
8
+ gt: ">",
9
+ lt: "<",
10
+ gteq: ">=",
11
+ lteq: "<=",
12
+ // between: "between",
13
+ is_true: "= true",
14
+ is_false: "= false",
15
+ is_null: "is null",
16
+ is_not_null: "is not null"
17
+ };
18
+
19
+ const TEXT_OPERATORS = ["eq", "not_eq", "matches", "does_not_match"];
20
+ const NUMBER_OPERATORS = ["eq", "not_eq", "gt", "lt", "gteq", "lteq"];
21
+ const DATE_OPERATORS = ["eq", "not_eq", "gt", "lt", "gteq", "lteq"];
22
+ const BOOLEAN_OPERATORS = ["eq", "not_eq", "is_true", "is_false", "is_null", "is_not_null"];
23
+
24
+ const INPUT_TYPE_BY_COLUMN = {
25
+ string: "text",
26
+ text: "text",
27
+ integer: "number",
28
+ float: "number",
29
+ decimal: "number",
30
+ datetime: "date",
31
+ date: "date",
32
+ time: "time"
33
+ };
34
+
35
+ const CL = {
36
+ row: "flex items-center py-1 border-l-1 border-border ps-1 ms-1",
37
+ select: "px-4 py-2 rounded-md border-1 border-border w-fit h-10 bg-panel",
38
+ selectMuted: "px-4 py-2 rounded-md border-1 border-border w-fit h-10 bg-background",
39
+ selectWhere: "px-4 py-2 rounded-md border-1 border-border w-fit h-10",
40
+ input: "px-4 py-2 rounded-md border-1 border-border w-fit max-w-40 h-10",
41
+ separator: "bg-panel inline-block h-2 w-3",
42
+ removeBtn: "text-red-500 hover:text-red-800"
43
+ };
44
+
45
+ function operatorsForColumnType(type) {
46
+ if (type === "text" || type === "string") return TEXT_OPERATORS;
47
+ if (type === "integer" || type === "float" || type === "decimal") return NUMBER_OPERATORS;
48
+ if (type === "datetime" || type === "date" || type === "time") return DATE_OPERATORS;
49
+ if (type === "boolean") return BOOLEAN_OPERATORS;
50
+ return TEXT_OPERATORS;
51
+ }
52
+
53
+ function fillOperatorOptions(select, operatorKeys) {
54
+ select.add(new Option("Operator", ""));
55
+ for (const key of operatorKeys) {
56
+ select.add(new Option(OPERATOR_LABELS[key], key));
57
+ }
58
+ }
59
+
60
+ // Connects to data-controller="filter"
61
+ export default class extends Controller {
62
+ static targets = ["selectColumn", "form", "closeButton", "removeIcon"];
63
+ static values = { columns: Array };
64
+
65
+ connect() {
66
+ this.previousValues = new WeakMap();
67
+ this.addFilter(null, false);
68
+ }
69
+
70
+ createSeparator() {
71
+ const el = document.createElement("div");
72
+ el.className = CL.separator;
73
+ return el;
74
+ }
75
+
76
+ chooseColumn(event) {
77
+ const columnSelect = event.target;
78
+ const selected = columnSelect.value;
79
+ let selectedAttribute = null;
80
+
81
+ this.columnsValue = this.columnsValue.map((c) => {
82
+ if (c.name === selected) {
83
+ selectedAttribute = c;
84
+ return { ...c, used: true };
85
+ }
86
+ if (c.name === this.previousValues.get(columnSelect)) {
87
+ return { ...c, used: false };
88
+ }
89
+ return c;
90
+ });
91
+
92
+ this.resetOptions();
93
+
94
+ if (this.previousValues.get(columnSelect)) {
95
+ while (
96
+ columnSelect.nextElementSibling &&
97
+ columnSelect.nextElementSibling.tagName !== "BUTTON"
98
+ ) {
99
+ columnSelect.nextElementSibling.remove();
100
+ }
101
+ }
102
+
103
+ if (!selectedAttribute) {
104
+ return;
105
+ }
106
+
107
+ const sep = this.createSeparator();
108
+ columnSelect.after(this.createInputField(selectedAttribute));
109
+ columnSelect.after(sep.cloneNode(true));
110
+ columnSelect.after(this.createOperatorField(selectedAttribute));
111
+ columnSelect.after(sep.cloneNode(true));
112
+ }
113
+
114
+ rememberValue(event) {
115
+ this.previousValues.set(event.target, event.target.value);
116
+ }
117
+
118
+ addFilter(e, withOperatorType = true) {
119
+ e?.preventDefault();
120
+
121
+ const row = document.createElement("div");
122
+ row.className = CL.row;
123
+
124
+ const columnSelect = this.buildColumnSelect();
125
+ const removeButton = this.buildRemoveButton();
126
+
127
+ row.appendChild(removeButton);
128
+ row.appendChild(withOperatorType ? this.createOperatorTypeField() : this.createWhereField());
129
+ row.appendChild(this.createSeparator());
130
+ row.appendChild(columnSelect);
131
+
132
+ this.formTarget.lastElementChild.before(row);
133
+ }
134
+
135
+ buildColumnSelect() {
136
+ const select = document.createElement("select");
137
+ select.add(new Option("Column", ""));
138
+ this.columnsValue.forEach((col) => {
139
+ if (!col.used) {
140
+ select.add(new Option(col.name, col.name));
141
+ }
142
+ });
143
+ select.setAttribute("data-filter-target", "selectColumn");
144
+ select.setAttribute(
145
+ "data-action",
146
+ "change->filter#chooseColumn mousedown->filter#rememberValue"
147
+ );
148
+ select.className = CL.select;
149
+ return select;
150
+ }
151
+
152
+ buildRemoveButton() {
153
+ const button = document.createElement("button");
154
+ button.setAttribute("data-action", "click->filter#removeFilter");
155
+ button.className = CL.removeBtn;
156
+ const xIcon = this.removeIconTarget.cloneNode(true);
157
+ xIcon.classList.remove("hidden");
158
+ xIcon.classList.add("inline-block");
159
+ button.appendChild(xIcon);
160
+ return button;
161
+ }
162
+
163
+ createInputField(selectedAttribute) {
164
+ const input = document.createElement("input");
165
+ input.type = INPUT_TYPE_BY_COLUMN[selectedAttribute.type] || "text";
166
+ input.className = CL.input;
167
+ input.name = `filter[${selectedAttribute.name}][value]`;
168
+ return input;
169
+ }
170
+
171
+ createOperatorField(selectedAttribute) {
172
+ const select = document.createElement("select");
173
+ select.name = `filter[${selectedAttribute.name}][operator]`;
174
+ select.className = CL.selectMuted;
175
+ fillOperatorOptions(select, operatorsForColumnType(selectedAttribute.type));
176
+ return select;
177
+ }
178
+
179
+ createOperatorTypeField() {
180
+ const select = document.createElement("select");
181
+ select.name = "filter[operator_types][]";
182
+ select.className = CL.selectMuted;
183
+ select.add(new Option("Operator Type", "and"));
184
+ select.add(new Option("AND", "and"));
185
+ select.add(new Option("OR", "or"));
186
+ return select;
187
+ }
188
+
189
+ createWhereField() {
190
+ const select = document.createElement("select");
191
+ select.className = CL.selectWhere;
192
+ select.add(new Option("Where", "Where"));
193
+ select.disabled = true;
194
+ return select;
195
+ }
196
+
197
+ removeFilter(event) {
198
+ const row = event.currentTarget.parentElement;
199
+ const columnName = row?.querySelector("[data-filter-target='selectColumn']")?.value;
200
+
201
+ if (columnName) {
202
+ this.columnsValue = this.columnsValue.map((c) =>
203
+ c.name === columnName ? { ...c, used: false } : c
204
+ );
205
+ }
206
+
207
+ row?.remove();
208
+ this.resetOptions();
209
+ }
210
+
211
+ resetOptions() {
212
+ this.selectColumnTargets.forEach((select) => {
213
+ const value = select.value;
214
+ select.innerHTML = "";
215
+ select.add(new Option("Column", ""));
216
+ this.columnsValue.forEach((col) => {
217
+ if (!col.used || col.name === value) {
218
+ select.add(new Option(col.name, col.name));
219
+ }
220
+ });
221
+ select.value = value;
222
+ });
223
+ }
224
+ }
@@ -0,0 +1,18 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="flash"
4
+ export default class extends Controller {
5
+ static targets = [];
6
+
7
+ connect() {
8
+ setTimeout(() => {
9
+ this.element.classList.add("opacity-0");
10
+ setTimeout(() => this.element.remove(), 1000);
11
+ }, 4000);
12
+ }
13
+
14
+ close() {
15
+ this.element.classList.add("opacity-0");
16
+ setTimeout(() => this.element.remove(), 1000);
17
+ }
18
+ }
@@ -0,0 +1,193 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { Graph, InternalEvent, HierarchicalLayout } from "@maxgraph/core";
3
+ import "databasium/shapes/erd_table_shape";
4
+
5
+ // Connects to data-controller="graph"
6
+ export default class extends Controller {
7
+ static values = { tables: String };
8
+
9
+ connect() {
10
+ const data = JSON.parse(this.tablesValue);
11
+
12
+ const container = this.element;
13
+ InternalEvent.disableContextMenu(container);
14
+ const graph = new Graph(container);
15
+ graph.setPanning(true);
16
+
17
+ const panningHandler = graph.getPlugin("PanningHandler");
18
+ if (panningHandler) {
19
+ panningHandler.useLeftButtonForPanning = true;
20
+ }
21
+ container.style.cursor = "grab";
22
+
23
+ container.addEventListener(
24
+ "wheel",
25
+ (event) => {
26
+ event.preventDefault();
27
+
28
+ const currentScale = graph.view.scale;
29
+ const zoomFactor = event.deltaY < 0 ? 1.06 : 0.94;
30
+ const nextScale = Math.min(Math.max(currentScale * zoomFactor, 0.25), 2.5);
31
+
32
+ graph.zoomTo(nextScale, true);
33
+ },
34
+ { passive: false }
35
+ );
36
+
37
+ graph.getStylesheet().getDefaultEdgeStyle().edgeStyle = "orthogonalEdgeStyle";
38
+
39
+ const vertexes = this.addVertexes(graph, data);
40
+ const edges = this.addEdges(graph, data, vertexes);
41
+
42
+ const layout = new HierarchicalLayout(graph);
43
+ layout.execute(graph.getDefaultParent());
44
+ }
45
+
46
+ addLabelToEdge(graph, edge, text, position = "start") {
47
+ const x = position === "start" ? -0.85 : 0.85;
48
+
49
+ graph.insertVertex(
50
+ edge,
51
+ null,
52
+ text,
53
+ x,
54
+ 0,
55
+ 1,
56
+ 1,
57
+ {
58
+ fillColor: "none",
59
+ strokeColor: "none",
60
+ fontColor: "var(--color-main-text)",
61
+ fontSize: 12,
62
+ align: "center",
63
+ verticalAlign: "middle",
64
+ labelBackgroundColor: "var(--color-panel)",
65
+ labelPadding: 4
66
+ },
67
+ true // relative — without this, labels won't appear on the edge
68
+ );
69
+ }
70
+
71
+ addEdges(graph, data, vertexes) {
72
+ const edgeStyle = {
73
+ edgeStyle: "orthogonalEdgeStyle",
74
+ rounded: true,
75
+ startArrow: "none",
76
+ endArrow: "none",
77
+ strokeColor: "var(--color-border)",
78
+ strokeWidth: 2
79
+ };
80
+
81
+ graph.batchUpdate(() => {
82
+ const alreadySet = new Set();
83
+ for (let table of Object.keys(data)) {
84
+ for (let association of data[table]?.associations || []) {
85
+ const relatedTable = data[association.name];
86
+ if (!relatedTable || !vertexes[association.name]) continue;
87
+ if (alreadySet.has(`${association.name}-${table}`)) continue;
88
+
89
+ alreadySet.add(`${table}-${association.name}`);
90
+
91
+ const sourceMacro = association.macro;
92
+ const targetMacro = relatedTable.associations?.find(
93
+ (assoc) => assoc.name === table
94
+ )?.macro;
95
+
96
+ const edge = graph.insertEdge({
97
+ source: vertexes[table],
98
+ target: vertexes[association.name],
99
+ style: edgeStyle
100
+ });
101
+ // macros are declered on oposite sites, thats why we use labels for oposite macros
102
+ this.addLabelToEdge(
103
+ graph,
104
+ edge,
105
+ `${this.getLabelForEdge(sourceMacro)} (${targetMacro ?? "Missing relation"})`,
106
+ "end"
107
+ );
108
+ this.addLabelToEdge(
109
+ graph,
110
+ edge,
111
+ `${this.getLabelForEdge(targetMacro)} (${sourceMacro ?? "Missing relation"})`,
112
+ "start"
113
+ );
114
+ }
115
+ }
116
+ });
117
+ }
118
+
119
+ getLabelForEdge(macro) {
120
+ switch (macro) {
121
+ case "has_many":
122
+ return "0..*";
123
+ case "has_one":
124
+ return "1";
125
+ case "belongs_to":
126
+ return "1";
127
+ case "has_and_belongs_to_many":
128
+ return "0..*";
129
+ default:
130
+ return "0..*";
131
+ }
132
+ }
133
+
134
+ addZooming(graph) {
135
+ this.element.addEventListener(
136
+ "wheel",
137
+ (event) => {
138
+ event.preventDefault();
139
+
140
+ const currentScale = graph.view.scale;
141
+ const zoomFactor = event.deltaY < 0 ? 1.06 : 0.94;
142
+ const nextScale = Math.min(Math.max(currentScale * zoomFactor, 0.25), 2.5);
143
+
144
+ graph.zoomTo(nextScale, true);
145
+ },
146
+ { passive: false }
147
+ );
148
+ }
149
+
150
+ addVertexes(graph, data) {
151
+ const vertexes = {};
152
+ const parent = graph.getDefaultParent();
153
+
154
+ graph.batchUpdate(() => {
155
+ for (let table of Object.keys(data)) {
156
+ let longest = this.detectedMaxLength(data[table], "name");
157
+ let longestValue = this.detectedMaxLength(data[table], "sql_type");
158
+ let width = (longest + longestValue + 4) * 8;
159
+ const vertex = graph.insertVertex(
160
+ parent,
161
+ null,
162
+ {
163
+ name: table,
164
+ fields: this.getFields(data[table].columns)
165
+ },
166
+ 0,
167
+ 0,
168
+ width,
169
+ data[table].columns.filter((column) => column != undefined).length * 32 + 32,
170
+ { shape: "erdTable", label: "", fontSize: 0, perimeter: "rectanglePerimeter" }
171
+ );
172
+ vertexes[table] = vertex;
173
+ }
174
+ });
175
+ return vertexes;
176
+ }
177
+
178
+ getFields(columns) {
179
+ return columns
180
+ .filter((column) => column != undefined)
181
+ .map((column) => {
182
+ return { name: column.name, sql_type: column.sql_type };
183
+ });
184
+ }
185
+
186
+ detectedMaxLength(table, type) {
187
+ return (
188
+ table.columns.reduce((max, column) => {
189
+ return Math.max(max, column[type].length);
190
+ }, 0) || 0
191
+ );
192
+ }
193
+ }
@@ -0,0 +1,7 @@
1
+ import { Application } from "@hotwired/stimulus";
2
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
3
+
4
+ const application = Application.start();
5
+ window.Stimulus = application;
6
+
7
+ eagerLoadControllersFrom("databasium/controllers", application);
@@ -0,0 +1,13 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="layout"
4
+ export default class extends Controller {
5
+ static targets = ["sidebar"];
6
+
7
+ connect() {}
8
+
9
+ toggleSidebar(event) {
10
+ event.preventDefault();
11
+ this.sidebarTarget.classList.toggle("hidden");
12
+ }
13
+ }
@@ -0,0 +1,32 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="model"
4
+ export default class extends Controller {
5
+ static targets = [
6
+ "attribute",
7
+ "validation",
8
+ "relation",
9
+ "attributesContainer",
10
+ "validationsContainer",
11
+ "relationsContainer"
12
+ ];
13
+
14
+ connect() {}
15
+
16
+ add(event) {
17
+ const target = event.params["target"];
18
+ const container = event.params["container"];
19
+
20
+ if (!target || !container) return;
21
+
22
+ const capitalizedTarget = target[0].toUpperCase() + target.slice(1);
23
+
24
+ if (!this[`has${capitalizedTarget}Target`]) return;
25
+
26
+ const scope = event.currentTarget.closest("[data-controller~='attribute']") || this.element;
27
+ const destination = scope.querySelector(`[data-model-target='${container}']`);
28
+ if (!destination) return;
29
+
30
+ destination.insertAdjacentHTML("beforeend", this[`${target}Target`].innerHTML);
31
+ }
32
+ }