mensa 0.1.0 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/gem-push.yml +11 -0
  3. data/.gitignore +12 -0
  4. data/Gemfile +10 -8
  5. data/Gemfile.lock +299 -0
  6. data/MIT-LICENSE +20 -0
  7. data/Procfile +2 -0
  8. data/README.md +76 -18
  9. data/Rakefile +5 -13
  10. data/app/assets/config/mensa_manifest.js +5 -0
  11. data/app/assets/images/mensa/.keep +0 -0
  12. data/app/assets/stylesheets/mensa/application.css +11 -0
  13. data/app/components/mensa/add_filter/component.css +13 -0
  14. data/app/components/mensa/add_filter/component.html.slim +16 -0
  15. data/app/components/mensa/add_filter/component.rb +13 -0
  16. data/app/components/mensa/add_filter/component_controller.js +84 -0
  17. data/app/components/mensa/application_component.rb +12 -0
  18. data/app/components/mensa/cell/component.css +0 -0
  19. data/app/components/mensa/cell/component.html.slim +1 -0
  20. data/app/components/mensa/cell/component.rb +15 -0
  21. data/app/components/mensa/control_bar/component.css +5 -0
  22. data/app/components/mensa/control_bar/component.html.slim +15 -0
  23. data/app/components/mensa/control_bar/component.rb +21 -0
  24. data/app/components/mensa/filters/component.css +14 -0
  25. data/app/components/mensa/filters/component.html.slim +14 -0
  26. data/app/components/mensa/filters/component.rb +13 -0
  27. data/app/components/mensa/filters/component_controller.js +14 -0
  28. data/app/components/mensa/header/component.css +54 -0
  29. data/app/components/mensa/header/component.html.slim +8 -0
  30. data/app/components/mensa/header/component.rb +19 -0
  31. data/app/components/mensa/row_action/component.css +0 -0
  32. data/app/components/mensa/row_action/component.html.slim +6 -0
  33. data/app/components/mensa/row_action/component.rb +19 -0
  34. data/app/components/mensa/search/component.css +13 -0
  35. data/app/components/mensa/search/component.html.slim +17 -0
  36. data/app/components/mensa/search/component.rb +13 -0
  37. data/app/components/mensa/search/component_controller.js +62 -0
  38. data/app/components/mensa/table/component.css +0 -0
  39. data/app/components/mensa/table/component.html.slim +7 -0
  40. data/app/components/mensa/table/component.rb +16 -0
  41. data/app/components/mensa/table/component_controller.js +72 -0
  42. data/app/components/mensa/table_row/component.css +0 -0
  43. data/app/components/mensa/table_row/component.html.slim +6 -0
  44. data/app/components/mensa/table_row/component.rb +19 -0
  45. data/app/components/mensa/view/component.css +78 -0
  46. data/app/components/mensa/view/component.html.slim +17 -0
  47. data/app/components/mensa/view/component.rb +16 -0
  48. data/app/components/mensa/views/component.css +13 -0
  49. data/app/components/mensa/views/component.html.slim +18 -0
  50. data/app/components/mensa/views/component.rb +14 -0
  51. data/app/controllers/concerns/.keep +0 -0
  52. data/app/controllers/mensa/application_controller.rb +4 -0
  53. data/app/controllers/mensa/tables/filters_controller.rb +29 -0
  54. data/app/controllers/mensa/tables_controller.rb +37 -0
  55. data/app/helpers/mensa/application_helper.rb +5 -0
  56. data/app/helpers/mensa/tables_helper.rb +5 -0
  57. data/app/javascript/mensa/application.js +3 -0
  58. data/app/javascript/mensa/controllers/alert_controller.js +7 -0
  59. data/app/javascript/mensa/controllers/application.js +8 -0
  60. data/app/javascript/mensa/controllers/application_controller.js +57 -0
  61. data/app/javascript/mensa/controllers/index.js +24 -0
  62. data/app/jobs/mensa/application_job.rb +7 -0
  63. data/app/jobs/mensa/export_job.rb +91 -0
  64. data/app/models/concerns/.keep +0 -0
  65. data/app/models/mensa/application_record.rb +5 -0
  66. data/app/models/mensa/table_view.rb +5 -0
  67. data/app/tables/mensa/action.rb +26 -0
  68. data/app/tables/mensa/base.rb +103 -0
  69. data/app/tables/mensa/cell.rb +50 -0
  70. data/app/tables/mensa/column.rb +85 -0
  71. data/app/tables/mensa/config/action_dsl.rb +12 -0
  72. data/app/tables/mensa/config/column_dsl.rb +23 -0
  73. data/app/tables/mensa/config/dsl_logic.rb +90 -0
  74. data/app/tables/mensa/config/filter_dsl.rb +8 -0
  75. data/app/tables/mensa/config/render_dsl.rb +8 -0
  76. data/app/tables/mensa/config/table_dsl.rb +71 -0
  77. data/app/tables/mensa/config_readers.rb +13 -0
  78. data/app/tables/mensa/filter.rb +31 -0
  79. data/app/tables/mensa/row.rb +36 -0
  80. data/app/tables/mensa/scope.rb +99 -0
  81. data/app/views/layouts/mensa/application.html.slim +11 -0
  82. data/app/views/mensa/tables/filters/show.turbo_stream.slim +8 -0
  83. data/app/views/mensa/tables/index.html.slim +93 -0
  84. data/app/views/mensa/tables/show.html.slim +5 -0
  85. data/app/views/mensa/tables/show.turbo_stream.slim +5 -0
  86. data/bin/importmap +4 -0
  87. data/bin/rails +14 -0
  88. data/config/importmap.rb +8 -0
  89. data/config/locales/en.yml +8 -0
  90. data/config/locales/nl.yml +11 -0
  91. data/config/routes.rb +7 -0
  92. data/db/migrate/20240201184752_create_mensa_table_views.rb +15 -0
  93. data/lib/generators/mensa/install_generator.rb +24 -0
  94. data/lib/generators/mensa/tailwind_config_generator.rb +24 -0
  95. data/lib/generators/mensa/templates/config/initializers/mensa.rb +22 -0
  96. data/lib/mensa/configuration.rb +90 -0
  97. data/lib/mensa/engine.rb +37 -0
  98. data/lib/mensa/version.rb +1 -3
  99. data/lib/mensa.rb +24 -5
  100. data/lib/tasks/mensa_tasks.rake +13 -0
  101. data/mensa.gemspec +42 -0
  102. data/package.json +10 -0
  103. data/vendor/javascript/@rails--request.js.js +2 -0
  104. data/yarn.lock +837 -0
  105. metadata +245 -18
  106. data/.rubocop.yml +0 -13
  107. data/CHANGELOG.md +0 -5
  108. data/CODE_OF_CONDUCT.md +0 -84
  109. data/LICENSE.txt +0 -21
  110. data/sig/mensa.rbs +0 -4
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mensa
4
+ module Options
5
+ module ClassMethods
6
+ def option(name, default: nil)
7
+ attr_accessor(name)
8
+ schema[name] = default
9
+ end
10
+
11
+ def schema
12
+ @schema ||= {}
13
+ end
14
+ end
15
+
16
+ def set_defaults!
17
+ self.class.schema.each do |name, default|
18
+ instance_variable_set("@#{name}", default)
19
+ end
20
+ end
21
+
22
+ def self.included(cls)
23
+ cls.extend(ClassMethods)
24
+ end
25
+ end
26
+
27
+ class Configuration
28
+ include Options
29
+
30
+ option :logger, default: Rails.logger
31
+ option :base_controller, default: "::ApplicationController"
32
+
33
+ # TODO: add all icons below
34
+ option :icons, default: {
35
+ order_indicator_asc: "fa-solid fa-sort-up",
36
+ order_indicator_desc: "fa-solid fa-sort-down",
37
+ order_indicator: "fa-solid fa-sort",
38
+ control_bar_search: "fa-solid fa-magnifying-glass",
39
+ control_bar_filter: "fa-solid fa-filter",
40
+ control_bar_edit: "fa-solid fa-table-columns",
41
+ control_bar_compress: "fa-solid fa-compress",
42
+ control_bar_export: "fa-solid fa-file-export",
43
+ search: "fa-solid fa-magnifying-glass"
44
+ }
45
+ # PRO
46
+ # option :icons, default: {
47
+ # order_indicator_asc: "fa-regular fa-arrow-up-short-wide",
48
+ # order_indicator_desc: "fa-regular fa-arrow-down-wide-short",
49
+ # order_indicator: "fa-regular fa-arrow-down-arrow-up",
50
+ # control_bar_search: "fa-light fa-magnifying-glass",
51
+ # control_bar_filter: "fa-light fa-bars-filter",
52
+ # control_bar_edit: "fa-light fa-columns-3",
53
+ # control_bar_compress: "fa-light fa-compress",
54
+ # control_bar_export: "fa-light fa-file-export",
55
+ # search: "fa-light fa-magnifying-glass"
56
+ # }
57
+
58
+ option :callbacks, default: {
59
+ # Callback is called when the export is started,
60
+ # the return value is passed as context to the export_complete callback
61
+ export_started: lambda do |user_id, table_name|
62
+ end,
63
+ export_complete: lambda do |user_id, table_name, context, attachment|
64
+ end
65
+ }
66
+
67
+ def initialize
68
+ set_defaults!
69
+ end
70
+ end
71
+
72
+ module Configurable
73
+ attr_writer :config
74
+
75
+ def config
76
+ @config ||= Configuration.new
77
+ end
78
+
79
+ def configure
80
+ yield(config)
81
+ end
82
+
83
+ alias setup configure
84
+
85
+ def reset_config!
86
+ @config = Configuration.new
87
+ end
88
+ end
89
+
90
+ end
@@ -0,0 +1,37 @@
1
+ require 'pagy'
2
+ require 'satis'
3
+ require 'textacular'
4
+ require 'caxlsx_rails'
5
+ require 'slim'
6
+ require 'tailwindcss-rails'
7
+ require "importmap-rails"
8
+ require "turbo-rails"
9
+ require "stimulus-rails"
10
+
11
+ module Mensa
12
+ class Engine < ::Rails::Engine
13
+ isolate_namespace Mensa
14
+
15
+ initializer 'mensa.assets' do |app|
16
+ app.config.assets.paths << root.join("app/javascript")
17
+ app.config.assets.paths << root.join("app/components")
18
+ app.config.assets.paths << Mensa::Engine.root.join("vendor/javascript")
19
+ app.config.assets.precompile += %w[mensa_manifest]
20
+ end
21
+
22
+ initializer 'mensa.importmap', before: "importmap" do |app|
23
+ app.config.importmap.paths << root.join("config/importmap.rb")
24
+ app.config.importmap.cache_sweepers << root.join("app/javascript")
25
+ app.config.importmap.cache_sweepers << root.join("app/components")
26
+ app.config.importmap.cache_sweepers << Mensa::Engine.root.join("vendor/javascript")
27
+ end
28
+
29
+ initializer 'mensa.helper' do
30
+ Rails.application.reloader.to_prepare do
31
+ ActiveSupport.on_load(:action_controller) do
32
+ Satis.add_helper(:table, ::Mensa::Table::Component)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/mensa/version.rb CHANGED
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Mensa
4
- VERSION = "0.1.0"
2
+ VERSION = "0.1.4"
5
3
  end
data/lib/mensa.rb CHANGED
@@ -1,8 +1,27 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "mensa/version"
1
+ require "mensa/version"
2
+ require "mensa/engine"
3
+ require 'mensa/configuration'
4
+ require "importmap-rails"
4
5
 
5
6
  module Mensa
6
- class Error < StandardError; end
7
- # Your code goes here...
7
+ extend Configurable
8
+
9
+ class << self
10
+ def for_name(name, config = {})
11
+ instance = class_for_name(name).new(config)
12
+ instance.name = name
13
+ instance
14
+ end
15
+
16
+ def class_for_name(name)
17
+ class_name = "#{name}_table".camelcase
18
+ unless class_name.safe_constantize
19
+ module_class_name = name.to_s.split('_', 2).map(&:camelcase).join('::') + 'Table'
20
+ class_name = module_class_name if module_class_name.safe_constantize
21
+ end
22
+ Kernel.const_get("::#{class_name}")
23
+ rescue NameError
24
+ raise NotImplementedError, "No '#{name}' table defined."
25
+ end
26
+ end
8
27
  end
@@ -0,0 +1,13 @@
1
+ namespace :mensa do
2
+ namespace :tailwindcss do
3
+ desc "Configure your Tailwind CSS"
4
+ task :config do
5
+ Rails::Generators.invoke("mensa:tailwind_config", ["--force"])
6
+ end
7
+ end
8
+ end
9
+
10
+ if Rake::Task.task_defined?("tailwindcss:build")
11
+ Rake::Task["tailwindcss:build"].enhance(["mensa:tailwindcss:config"])
12
+ Rake::Task["tailwindcss:watch"].enhance(["mensa:tailwindcss:config"])
13
+ end
data/mensa.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ require_relative "lib/mensa/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "mensa"
5
+ spec.version = Mensa::VERSION
6
+ spec.authors = ["Tom de Grunt"]
7
+ spec.email = ["tom@degrunt.nl"]
8
+ spec.homepage = "https://github.com/entdec/mensa"
9
+ spec.summary = "Fast and awesome tables"
10
+ spec.description = "Fast and awesome tables, with pagination, sorting, filtering and custom views."
11
+ spec.license = "MIT"
12
+
13
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the "allowed_push_host"
14
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
15
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
16
+
17
+ spec.post_install_message = <<~MESSAGE
18
+ Mensa requires additional setup. Please run the following
19
+ command to install the necessary files:
20
+
21
+ bin/rails mensa:install:migrations
22
+ MESSAGE
23
+
24
+ spec.metadata["homepage_uri"] = spec.homepage
25
+ spec.metadata["source_code_uri"] = "https://github.com/entdec/mensa"
26
+ spec.metadata["changelog_uri"] = "https://github.com/entdec/mensa/CHANGELOG"
27
+
28
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
29
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
30
+ end
31
+
32
+ spec.add_dependency 'caxlsx_rails', '~> 0'
33
+ spec.add_dependency "rails", ">= 7.1"
34
+ spec.add_dependency 'pagy', '>=6'
35
+ spec.add_dependency 'textacular', '>=5'
36
+
37
+ spec.add_dependency 'slim'
38
+ spec.add_dependency 'tailwindcss-rails'
39
+ spec.add_dependency 'importmap-rails'
40
+ spec.add_dependency 'turbo-rails'
41
+ spec.add_dependency 'stimulus-rails'
42
+ end
data/package.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@entdec/mensa",
3
+ "dependencies": {
4
+ "postcss-advanced-variables": "^3.0.1",
5
+ "postcss-comment": "^2.0.0",
6
+ "postcss-import": "^16.0.1",
7
+ "postcss-mixins": "^9.0.4",
8
+ "tailwindcss": "^3.4.1"
9
+ }
10
+ }
@@ -0,0 +1,2 @@
1
+ class FetchResponse{constructor(t){this.response=t}get statusCode(){return this.response.status}get redirected(){return this.response.redirected}get ok(){return this.response.ok}get unauthenticated(){return 401===this.statusCode}get unprocessableEntity(){return 422===this.statusCode}get authenticationURL(){return this.response.headers.get("WWW-Authenticate")}get contentType(){const t=this.response.headers.get("Content-Type")||"";return t.replace(/;.*$/,"")}get headers(){return this.response.headers}get html(){return this.contentType.match(/^(application|text)\/(html|xhtml\+xml)$/)?this.text:Promise.reject(new Error(`Expected an HTML response but got "${this.contentType}" instead`))}get json(){return this.contentType.match(/^application\/.*json$/)?this.responseJson||(this.responseJson=this.response.json()):Promise.reject(new Error(`Expected a JSON response but got "${this.contentType}" instead`))}get text(){return this.responseText||(this.responseText=this.response.text())}get isTurboStream(){return this.contentType.match(/^text\/vnd\.turbo-stream\.html/)}async renderTurboStream(){if(!this.isTurboStream)return Promise.reject(new Error(`Expected a Turbo Stream response but got "${this.contentType}" instead`));window.Turbo?await window.Turbo.renderStreamMessage(await this.text):console.warn("You must set `window.Turbo = Turbo` to automatically process Turbo Stream events with request.js")}}class RequestInterceptor{static register(t){this.interceptor=t}static get(){return this.interceptor}static reset(){this.interceptor=void 0}}function getCookie(t){const e=document.cookie?document.cookie.split("; "):[];const n=`${encodeURIComponent(t)}=`;const s=e.find((t=>t.startsWith(n)));if(s){const t=s.split("=").slice(1).join("=");if(t)return decodeURIComponent(t)}}function compact(t){const e={};for(const n in t){const s=t[n];void 0!==s&&(e[n]=s)}return e}function metaContent(t){const e=document.head.querySelector(`meta[name="${t}"]`);return e&&e.content}function stringEntriesFromFormData(t){return[...t].reduce(((t,[e,n])=>t.concat("string"===typeof n?[[e,n]]:[])),[])}function mergeEntries(t,e){for(const[n,s]of e)if(!(s instanceof window.File))if(t.has(n)&&!n.includes("[]")){t.delete(n);t.set(n,s)}else t.append(n,s)}class FetchRequest{constructor(t,e,n={}){this.method=t;this.options=n;this.originalUrl=e.toString()}async perform(){try{const t=RequestInterceptor.get();t&&await t(this)}catch(t){console.error(t)}const t=new FetchResponse(await window.fetch(this.url,this.fetchOptions));if(t.unauthenticated&&t.authenticationURL)return Promise.reject(window.location.href=t.authenticationURL);const e=t.ok||t.unprocessableEntity;e&&t.isTurboStream&&await t.renderTurboStream();return t}addHeader(t,e){const n=this.additionalHeaders;n[t]=e;this.options.headers=n}sameHostname(){if(!this.originalUrl.startsWith("http:"))return true;try{return new URL(this.originalUrl).hostname===window.location.hostname}catch(t){return true}}get fetchOptions(){return{method:this.method.toUpperCase(),headers:this.headers,body:this.formattedBody,signal:this.signal,credentials:this.credentials,redirect:this.redirect}}get headers(){const t={"X-Requested-With":"XMLHttpRequest","Content-Type":this.contentType,Accept:this.accept};this.sameHostname()&&(t["X-CSRF-Token"]=this.csrfToken);return compact(Object.assign(t,this.additionalHeaders))}get csrfToken(){return getCookie(metaContent("csrf-param"))||metaContent("csrf-token")}get contentType(){return this.options.contentType?this.options.contentType:null==this.body||this.body instanceof window.FormData?void 0:this.body instanceof window.File?this.body.type:"application/json"}get accept(){switch(this.responseKind){case"html":return"text/html, application/xhtml+xml";case"turbo-stream":return"text/vnd.turbo-stream.html, text/html, application/xhtml+xml";case"json":return"application/json, application/vnd.api+json";default:return"*/*"}}get body(){return this.options.body}get query(){const t=(this.originalUrl.split("?")[1]||"").split("#")[0];const e=new URLSearchParams(t);let n=this.options.query;n=n instanceof window.FormData?stringEntriesFromFormData(n):n instanceof window.URLSearchParams?n.entries():Object.entries(n||{});mergeEntries(e,n);const s=e.toString();return s.length>0?`?${s}`:""}get url(){return this.originalUrl.split("?")[0].split("#")[0]+this.query}get responseKind(){return this.options.responseKind||"html"}get signal(){return this.options.signal}get redirect(){return this.options.redirect||"follow"}get credentials(){return this.options.credentials||"same-origin"}get additionalHeaders(){return this.options.headers||{}}get formattedBody(){const t="[object String]"===Object.prototype.toString.call(this.body);const e="application/json"===this.headers["Content-Type"];return e&&!t?JSON.stringify(this.body):this.body}}async function get(t,e){const n=new FetchRequest("get",t,e);return n.perform()}async function post(t,e){const n=new FetchRequest("post",t,e);return n.perform()}async function put(t,e){const n=new FetchRequest("put",t,e);return n.perform()}async function patch(t,e){const n=new FetchRequest("patch",t,e);return n.perform()}async function destroy(t,e){const n=new FetchRequest("delete",t,e);return n.perform()}export{FetchRequest,FetchResponse,RequestInterceptor,destroy,get,patch,post,put};
2
+