inner_plan 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +28 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/config/inner_plan_manifest.js +6 -0
  5. data/app/assets/stylesheets/inner_plan/application.css +24 -0
  6. data/app/components/application_view.rb +4 -0
  7. data/app/components/inner_plan/breadcrumbs_component.rb +26 -0
  8. data/app/components/inner_plan/groups/edit_view.rb +72 -0
  9. data/app/components/inner_plan/groups/new_view.rb +66 -0
  10. data/app/components/inner_plan/groups/row/handle_component.rb +30 -0
  11. data/app/components/inner_plan/groups/row_component.rb +31 -0
  12. data/app/components/inner_plan/groups/rows_component.rb +17 -0
  13. data/app/components/inner_plan/groups/show_menu_component.rb +30 -0
  14. data/app/components/inner_plan/groups/show_view.rb +77 -0
  15. data/app/components/inner_plan/lists/completed_tasks_component.rb +30 -0
  16. data/app/components/inner_plan/lists/edit_view.rb +62 -0
  17. data/app/components/inner_plan/lists/index_view.rb +57 -0
  18. data/app/components/inner_plan/lists/new_view.rb +49 -0
  19. data/app/components/inner_plan/lists/ongoing_tasks_component.rb +17 -0
  20. data/app/components/inner_plan/lists/row/handle_component.rb +37 -0
  21. data/app/components/inner_plan/lists/row_component.rb +55 -0
  22. data/app/components/inner_plan/lists/show_menu_component.rb +37 -0
  23. data/app/components/inner_plan/lists/show_view.rb +76 -0
  24. data/app/components/inner_plan/progress_bar_separator_component.rb +20 -0
  25. data/app/components/inner_plan/tasks/completed_toggler_component.rb +27 -0
  26. data/app/components/inner_plan/tasks/edit_view.rb +82 -0
  27. data/app/components/inner_plan/tasks/form/item_component.rb +21 -0
  28. data/app/components/inner_plan/tasks/form/row_component.rb +19 -0
  29. data/app/components/inner_plan/tasks/inline_form_view.rb +62 -0
  30. data/app/components/inner_plan/tasks/row/addons_component.rb +22 -0
  31. data/app/components/inner_plan/tasks/row/base_addon_component.rb +19 -0
  32. data/app/components/inner_plan/tasks/row/description_addon_component.rb +13 -0
  33. data/app/components/inner_plan/tasks/row/due_on_addon_component.rb +14 -0
  34. data/app/components/inner_plan/tasks/row/group_addon_component.rb +11 -0
  35. data/app/components/inner_plan/tasks/row/handle_component.rb +40 -0
  36. data/app/components/inner_plan/tasks/row_component.rb +35 -0
  37. data/app/components/inner_plan/tasks/rows_component.rb +22 -0
  38. data/app/components/inner_plan/tasks/show/item_component.rb +20 -0
  39. data/app/components/inner_plan/tasks/show/row_component.rb +19 -0
  40. data/app/components/inner_plan/tasks/show_menu_component.rb +30 -0
  41. data/app/components/inner_plan/tasks/show_view.rb +76 -0
  42. data/app/components/inner_plan/user_with_avatar_component.rb +17 -0
  43. data/app/concepts/inner_plan/list/operation/index.rb +11 -0
  44. data/app/concepts/inner_plan/task/operation/complete.rb +12 -0
  45. data/app/concepts/inner_plan/task/operation/create.rb +46 -0
  46. data/app/concepts/inner_plan/task/operation/reopen.rb +12 -0
  47. data/app/concepts/inner_plan/task/operation/update.rb +25 -0
  48. data/app/concepts/inner_plan/task/operation/update_position.rb +25 -0
  49. data/app/controllers/inner_plan/application_controller.rb +12 -0
  50. data/app/controllers/inner_plan/groups_controller.rb +59 -0
  51. data/app/controllers/inner_plan/lists_controller.rb +59 -0
  52. data/app/controllers/inner_plan/tasks_controller.rb +73 -0
  53. data/app/helpers/inner_plan/application_helper.rb +4 -0
  54. data/app/javascript/inner_plan/application.js +15 -0
  55. data/app/javascript/inner_plan/controllers/groups_controller.js +47 -0
  56. data/app/javascript/inner_plan/controllers/lists_controller.js +30 -0
  57. data/app/javascript/inner_plan/controllers/task_inline_form_controller.js +40 -0
  58. data/app/javascript/inner_plan/controllers/tasks_controller.js +47 -0
  59. data/app/jobs/inner_plan/application_job.rb +4 -0
  60. data/app/mailers/inner_plan/application_mailer.rb +6 -0
  61. data/app/models/inner_plan/application_record.rb +5 -0
  62. data/app/models/inner_plan/list.rb +29 -0
  63. data/app/models/inner_plan/task.rb +36 -0
  64. data/app/services/inner_plan/tasks/description_renderer.rb +18 -0
  65. data/app/services/inner_plan/tasks/title_renderer.rb +18 -0
  66. data/app/views/inner_plan/shared/_blankslate.svg.erb +1 -0
  67. data/app/views/inner_plan/tasks/complete.turbo_stream.erb +16 -0
  68. data/app/views/inner_plan/tasks/create.turbo_stream.erb +11 -0
  69. data/app/views/inner_plan/tasks/create_failure.turbo_stream.erb +5 -0
  70. data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
  71. data/app/views/layouts/inner_plan/application.html.erb +38 -0
  72. data/config/importmap.rb +8 -0
  73. data/config/routes.rb +17 -0
  74. data/db/migrate/20241205203943_create_inner_plan_tables.rb +37 -0
  75. data/db/seeds.rb +14 -0
  76. data/lib/inner_plan/configuration.rb +116 -0
  77. data/lib/inner_plan/engine.rb +22 -0
  78. data/lib/inner_plan/smart_array.rb +66 -0
  79. data/lib/inner_plan/version.rb +3 -0
  80. data/lib/inner_plan.rb +28 -0
  81. data/lib/tasks/inner_plan_tasks.rake +4 -0
  82. metadata +265 -0
@@ -0,0 +1,29 @@
1
+ module InnerPlan
2
+ class List < ApplicationRecord
3
+ has_many :tasks, class_name: 'InnerPlan::Task'
4
+ has_many :tasks_including_groups, ->(list){ rewhere(list_id: ([list.id] + list.lists.pluck(:id))) }, class_name: 'InnerPlan::Task'
5
+ has_many :lists, foreign_key: :parent_id
6
+ belongs_to :list, foreign_key: :parent_id, optional: true
7
+ belongs_to :user, class_name: InnerPlan.configuration.user_class_name
8
+
9
+ positioned
10
+
11
+ scope :ordered_by_position, ->{ order(position: :asc) }
12
+ scope :root, ->{ where(parent_id: nil) }
13
+ scope :sub, ->{ where.not(parent_id: nil) }
14
+
15
+ validates :title, presence: true
16
+
17
+ def root?
18
+ parent_id.blank?
19
+ end
20
+
21
+ def sub?
22
+ parent_id.present?
23
+ end
24
+
25
+ def to_param
26
+ [id.to_s, title.to_url(limit: 50, truncate_words: false)].join('-')
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ module InnerPlan
2
+ class Task < ApplicationRecord
3
+ belongs_to :list
4
+ belongs_to :user, class_name: InnerPlan.configuration.user_class_name,
5
+ optional: true
6
+
7
+ positioned on: :list
8
+
9
+ scope :ongoing, ->{ where(completed_at: nil) }
10
+ scope :completed, ->{ where.not(completed_at: nil).order(completed_at: :desc) }
11
+ scope :ordered_by_position, ->{ order(position: :asc) }
12
+ scope :recently_completed_first, -> { order(completed_at: :desc) }
13
+
14
+ validates :title, presence: true
15
+
16
+ def ongoing?
17
+ !completed?
18
+ end
19
+
20
+ def completed?
21
+ completed_at.present?
22
+ end
23
+
24
+ def complete!
25
+ touch(:completed_at)
26
+ end
27
+
28
+ def reopen!
29
+ update(completed_at: nil)
30
+ end
31
+
32
+ def to_param
33
+ [id.to_s, title.to_url(limit: 50, truncate_words: false)].join('-')
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ module InnerPlan::Tasks
2
+ class DescriptionRenderer < Trailblazer::Operation
3
+ include ActionView::Helpers::TextHelper
4
+
5
+ step :extract_description
6
+ step :apply_simple_format
7
+
8
+ private
9
+
10
+ def extract_description(ctx, task:, **)
11
+ ctx[:description] = task.description
12
+ end
13
+
14
+ def apply_simple_format(ctx, description:, **)
15
+ ctx[:description] = simple_format(description)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module InnerPlan::Tasks
2
+ class TitleRenderer < Trailblazer::Operation
3
+ include ActionView::Helpers::SanitizeHelper
4
+
5
+ step :extract_title
6
+ step :sanitize_title
7
+
8
+ private
9
+
10
+ def extract_title(ctx, task:, **)
11
+ ctx[:title] = task.title
12
+ end
13
+
14
+ def sanitize_title(ctx, title:, **)
15
+ ctx[:title] = sanitize(title)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" class="opacity-75" data-name="Layer 1" width="300" viewBox="0 0 647.63626 632.17383" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M687.3279,276.08691H512.81813a15.01828,15.01828,0,0,0-15,15v387.85l-2,.61005-42.81006,13.11a8.00676,8.00676,0,0,1-9.98974-5.31L315.678,271.39691a8.00313,8.00313,0,0,1,5.31006-9.99l65.97022-20.2,191.25-58.54,65.96972-20.2a7.98927,7.98927,0,0,1,9.99024,5.3l32.5498,106.32Z" transform="translate(-276.18187 -133.91309)" fill="#f2f2f2"/><path d="M725.408,274.08691l-39.23-128.14a16.99368,16.99368,0,0,0-21.23-11.28l-92.75,28.39L380.95827,221.60693l-92.75,28.4a17.0152,17.0152,0,0,0-11.28028,21.23l134.08008,437.93a17.02661,17.02661,0,0,0,16.26026,12.03,16.78926,16.78926,0,0,0,4.96972-.75l63.58008-19.46,2-.62v-2.09l-2,.61-64.16992,19.65a15.01489,15.01489,0,0,1-18.73-9.95l-134.06983-437.94a14.97935,14.97935,0,0,1,9.94971-18.73l92.75-28.4,191.24024-58.54,92.75-28.4a15.15551,15.15551,0,0,1,4.40966-.66,15.01461,15.01461,0,0,1,14.32032,10.61l39.0498,127.56.62012,2h2.08008Z" transform="translate(-276.18187 -133.91309)" fill="#3f3d56"/><path d="M398.86279,261.73389a9.0157,9.0157,0,0,1-8.61133-6.3667l-12.88037-42.07178a8.99884,8.99884,0,0,1,5.9712-11.24023l175.939-53.86377a9.00867,9.00867,0,0,1,11.24072,5.9707l12.88037,42.07227a9.01029,9.01029,0,0,1-5.9707,11.24072L401.49219,261.33887A8.976,8.976,0,0,1,398.86279,261.73389Z" transform="translate(-276.18187 -133.91309)" fill="#6c63ff"/><circle cx="190.15351" cy="24.95465" r="20" fill="#6c63ff"/><circle cx="190.15351" cy="24.95465" r="12.66462" fill="#fff"/><path d="M878.81836,716.08691h-338a8.50981,8.50981,0,0,1-8.5-8.5v-405a8.50951,8.50951,0,0,1,8.5-8.5h338a8.50982,8.50982,0,0,1,8.5,8.5v405A8.51013,8.51013,0,0,1,878.81836,716.08691Z" transform="translate(-276.18187 -133.91309)" fill="#e6e6e6"/><path d="M723.31813,274.08691h-210.5a17.02411,17.02411,0,0,0-17,17v407.8l2-.61v-407.19a15.01828,15.01828,0,0,1,15-15H723.93825Zm183.5,0h-394a17.02411,17.02411,0,0,0-17,17v458a17.0241,17.0241,0,0,0,17,17h394a17.0241,17.0241,0,0,0,17-17v-458A17.02411,17.02411,0,0,0,906.81813,274.08691Zm15,475a15.01828,15.01828,0,0,1-15,15h-394a15.01828,15.01828,0,0,1-15-15v-458a15.01828,15.01828,0,0,1,15-15h394a15.01828,15.01828,0,0,1,15,15Z" transform="translate(-276.18187 -133.91309)" fill="#3f3d56"/><path d="M801.81836,318.08691h-184a9.01015,9.01015,0,0,1-9-9v-44a9.01016,9.01016,0,0,1,9-9h184a9.01016,9.01016,0,0,1,9,9v44A9.01015,9.01015,0,0,1,801.81836,318.08691Z" transform="translate(-276.18187 -133.91309)" fill="#6c63ff"/><circle cx="433.63626" cy="105.17383" r="20" fill="#6c63ff"/><circle cx="433.63626" cy="105.17383" r="12.18187" fill="#fff"/></svg>
@@ -0,0 +1,16 @@
1
+ <% if params[:context] == 'lists_index' %>
2
+ <turbo-stream action="update" target="<%= dom_id(@task.list, :ongoing_tasks) %>">
3
+ <template>
4
+ <%= render(InnerPlan::Lists::OngoingTasksComponent.new(@task.list, context: params[:context])) %>
5
+ </template>
6
+ </turbo-stream>
7
+
8
+ <% list = @task.list.root? ? @task.list : @task.list.list %>
9
+ <turbo-stream action="replace" target="<%= dom_id(list, :completed_tasks) %>">
10
+ <template>
11
+ <%= render(InnerPlan::Lists::CompletedTasksComponent.new(list, context: params[:context])) %>
12
+ </template>
13
+ </turbo-stream>
14
+ <% else %>
15
+ <turbo-stream action="refresh"></turbo-stream>
16
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <turbo-stream action="append" target="<%= dom_id(@list, :ongoing_tasks) %>">
2
+ <template>
3
+ <%= render(InnerPlan::Tasks::RowComponent.new(@task)) %>
4
+ </template>
5
+ </turbo-stream>
6
+
7
+ <turbo-stream action="update" target="<%= dom_id(@list, :add_task) %>">
8
+ <template>
9
+ <%= render InnerPlan::Tasks::InlineFormView.new(list: @list, form_visible: true) %>
10
+ </template>
11
+ </turbo-stream>
@@ -0,0 +1,5 @@
1
+ <turbo-stream action="update" target="<%= dom_id(@list, :add_task) %>">
2
+ <template>
3
+ <%= render InnerPlan::Tasks::InlineFormView.new(list: @list, task: @task, form_visible: true) %>
4
+ </template>
5
+ </turbo-stream>
@@ -0,0 +1,3 @@
1
+ <div class="trix-content">
2
+ <%= yield -%>
3
+ </div>
@@ -0,0 +1,38 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Inner plan</title>
5
+
6
+ <meta name="turbo-refresh-scroll" content="preserve">
7
+
8
+ <%= csrf_meta_tags %>
9
+ <%= csp_meta_tag %>
10
+
11
+ <script src="https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.12/dist/turbo.es2017-umd.min.js"></script>
12
+
13
+ <%= stylesheet_link_tag "inner_plan/application", media: "all" %>
14
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
15
+ <link href="https://cdn.jsdelivr.net/npm/trix@2.1.8/dist/trix.min.css" rel="stylesheet">
16
+
17
+ <%= javascript_importmap_tags "application", importmap: InnerPlan.importmap %>
18
+
19
+ <% InnerPlan.configuration.additional_importmap_modules.each do |importmap_module| %>
20
+ <script type="module">import "<%= importmap_module %>"</script>
21
+ <% end %>
22
+ </head>
23
+ <body>
24
+
25
+ <div class="container">
26
+ <div class="row justify-content-center my-5">
27
+ <div class="col-lg-8 col-md-10 col-12">
28
+ <div class="card" style="--bs-card-border-radius: 1rem;">
29
+ <div class="card-body p-5">
30
+ <%= yield %>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </div>
36
+
37
+ </body>
38
+ </html>
@@ -0,0 +1,8 @@
1
+ pin "application", to: "inner_plan/application.js", preload: true
2
+ pin "@hotwired/stimulus", to: "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js"
3
+ pin "sortablejs", to: "https://cdn.jsdelivr.net/npm/sortablejs@1.15.4/+esm"
4
+ pin "@rails/request.js", to: "https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.11/dist/requestjs.min.js"
5
+ pin "@popperjs/core", to: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/popper.min.js"
6
+ pin "bootstrap", to: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.esm.min.js"
7
+
8
+ pin_all_from InnerPlan::Engine.root.join("app/javascript/inner_plan/controllers"), under: "controllers", to: "inner_plan/controllers"
data/config/routes.rb ADDED
@@ -0,0 +1,17 @@
1
+ InnerPlan::Engine.routes.draw do
2
+ resources :lists, except: [:destroy] do
3
+ patch :update_position, on: :member
4
+
5
+ resources :tasks, shallow: true, except: [:destroy] do
6
+ patch :update_position, on: :member
7
+ post :complete, on: :member
8
+ post :reopen, on: :member
9
+ end
10
+
11
+ resources :groups, shallow: true, except: [:destroy] do
12
+ patch :update_position, on: :member
13
+ end
14
+ end
15
+
16
+ root to: 'lists#index'
17
+ end
@@ -0,0 +1,37 @@
1
+ class CreateInnerPlanTables < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table "inner_plan_lists", force: :cascade do |t|
4
+ t.string "title"
5
+ t.integer "position", default: 0
6
+ t.datetime "created_at", null: false
7
+ t.datetime "updated_at", null: false
8
+ t.integer "completed_tasks_count", default: 0
9
+ t.integer "tasks_count", default: 0
10
+ t.integer "ongoing_tasks_count", default: 0
11
+ t.integer "user_id"
12
+ t.integer "parent_id"
13
+ t.text "description"
14
+ t.index ["parent_id"], name: "index_inner_plan_lists_on_parent_id"
15
+ t.index ["position"], name: "index_inner_plan_lists_on_position", unique: true
16
+ t.index ["user_id"], name: "index_inner_plan_lists_on_user_id"
17
+ end
18
+
19
+ create_table "inner_plan_tasks", force: :cascade do |t|
20
+ t.string "title"
21
+ t.text "description"
22
+ t.datetime "completed_at"
23
+ t.datetime "created_at", null: false
24
+ t.datetime "updated_at", null: false
25
+ t.integer "position", default: 0
26
+ t.date "due_on"
27
+ t.integer "user_id"
28
+ t.integer "list_id"
29
+ t.index ["list_id", "position"], name: "index_inner_plan_tasks_on_list_id_and_position", unique: true
30
+ t.index ["list_id"], name: "index_inner_plan_tasks_on_list_id"
31
+ t.index ["user_id"], name: "index_inner_plan_tasks_on_user_id"
32
+ end
33
+
34
+ add_foreign_key "inner_plan_lists", "inner_plan_lists", column: "parent_id"
35
+ add_foreign_key "inner_plan_tasks", "inner_plan_lists", column: "list_id"
36
+ end
37
+ end
data/db/seeds.rb ADDED
@@ -0,0 +1,14 @@
1
+ user = User.create! email: 'email@example.com', password: 'ChangeMe666'
2
+ InnerPlan::Profile.create! user: user
3
+
4
+ list = InnerPlan::List.create! title: 'Work and such', user: user
5
+ sub = InnerPlan::sub.create! list: list, title: 'Default', default: true, user: user
6
+
7
+ InnerPlan::Task.create! sub: sub, title: 'Duplikowanie produktów nie kopiuje cen', user: user
8
+ InnerPlan::Task.create! sub: sub, title: 'na android rozwala fotki - maja zły wymiar', user: user
9
+ InnerPlan::Task.create! sub: sub, title: 'wymuszenie w formularzu adresu wpisania numeru ulicy osobno', user: user
10
+ InnerPlan::Task.create! sub: sub, title: 'Masowe promocje nie odejmują tylko dodają do cen', user: user
11
+ InnerPlan::Task.create! sub: sub, title: 'Duplikowanie produktów nie kopiuje cen', completed_at: Time.current, user: user
12
+ InnerPlan::Task.create! sub: sub, title: 'Kiedy stan na minusie a desired na zero, nie dodawać produktu do raportu dla szwalni', completed_at: Time.current, user: user
13
+ InnerPlan::Task.create! sub: sub, title: 'Dodatki do koszyka', completed_at: Time.current
14
+ InnerPlan::Task.create! sub: sub, title: 'zliczanie na home tylko zamówień opłaconych i odejmowanie anulowanych', completed_at: Time.current, user: user
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InnerPlan
4
+ class Configuration
5
+ attr_accessor :user_class_name,
6
+ :current_user_method,
7
+ :task_edit_view_rows,
8
+ :task_show_view_rows,
9
+ :task_row_addons,
10
+ :additional_importmap_modules
11
+
12
+ def initialize
13
+ @user_class_name = 'User'
14
+ @current_user_method = :current_user
15
+ @additional_importmap_modules = []
16
+
17
+ # Tasks::EditView rows
18
+ due_on_item = proc { |context:, form:|
19
+ InnerPlan::Tasks::Form::ItemComponent.new(icon: Phlex::Icons::Tabler::CalendarDue, title: 'Due on') do
20
+ context.plain form.date_field :due_on,
21
+ class: "form-control",
22
+ autofocus: (context.focus == :due_on)
23
+ end
24
+ }
25
+
26
+ description_item = proc { |context:, form:|
27
+ InnerPlan::Tasks::Form::ItemComponent.new(icon: Phlex::Icons::Tabler::FileText, title: 'Description') do
28
+ context.plain form.text_area(:description, class: 'form-control', autofocus: (context.focus == :description))
29
+ end
30
+ }
31
+
32
+ @task_edit_view_rows = InnerPlan::SmartArray.new([
33
+ InnerPlan::SmartArray::Item.new(:primary_row, {}, InnerPlan::SmartArray.new([
34
+ InnerPlan::SmartArray::Item.new(:due_on, { span: 6 }, due_on_item),
35
+ ])),
36
+
37
+ InnerPlan::SmartArray::Item.new(:secondary_row, {}, InnerPlan::SmartArray.new([
38
+ InnerPlan::SmartArray::Item.new(:description, { span: 12 }, description_item)
39
+ ]))
40
+ ])
41
+
42
+ # Tasks::ShowView rows
43
+ due_on_item = proc { |context:|
44
+ InnerPlan::Tasks::Show::ItemComponent.new(icon: Phlex::Icons::Tabler::CalendarDue, title: 'Due on') do
45
+ context.a(
46
+ href: (context.helpers.edit_task_path(context.task, focus: :due_on)),
47
+ class:
48
+ (
49
+ context.class_names(
50
+ "text-decoration-none",
51
+ "text-body-tertiary" => context.task.due_on.blank?,
52
+ "text-reset" => context.task.due_on.present?
53
+ )
54
+ )
55
+ ) do
56
+ if context.task.due_on
57
+ context.plain context.task.due_on.strftime("%a, %b %e, %Y")
58
+ else
59
+ context.plain " Select a date... "
60
+ end
61
+ end
62
+ end
63
+ }
64
+
65
+ description_item = proc { |context:|
66
+ InnerPlan::Tasks::Show::ItemComponent.new(icon: Phlex::Icons::Tabler::FileText, title: 'Description') do
67
+ if context.task.description.present?
68
+ context.plain context.description.to_s
69
+ else
70
+ context.a(
71
+ href: (context.helpers.edit_task_path(context.task, focus: :description)),
72
+ class:
73
+ (
74
+ context.class_names(
75
+ "text-decoration-none",
76
+ "text-body-tertiary" => context.task.description.blank?,
77
+ "text-reset" => context.task.description.present?
78
+ )
79
+ )
80
+ ) { " Click to add description... " }
81
+ end
82
+ end
83
+ }
84
+
85
+ created_by_item = proc { |context:|
86
+ InnerPlan::Tasks::Show::ItemComponent.new(icon: Phlex::Icons::Tabler::CirclePlus, title: 'Created By') do
87
+ context.div(class: "text-body-tertiary") do
88
+ context.render(InnerPlan::UserWithAvatarComponent.new(context.task.user))
89
+ context.plain " on "
90
+ context.plain context.task.created_at.strftime("%a, %b %e, %Y")
91
+ end
92
+ end
93
+ }
94
+
95
+ @task_show_view_rows = InnerPlan::SmartArray.new([
96
+ InnerPlan::SmartArray::Item.new(:primary_row, {}, InnerPlan::SmartArray.new([
97
+ InnerPlan::SmartArray::Item.new(:due_on, { span: 6 }, due_on_item),
98
+ ])),
99
+
100
+ InnerPlan::SmartArray::Item.new(:secondary_row, {}, InnerPlan::SmartArray.new([
101
+ InnerPlan::SmartArray::Item.new(:description, { span: 12 }, description_item)
102
+ ])),
103
+
104
+ InnerPlan::SmartArray::Item.new(:tertiary_row, {}, InnerPlan::SmartArray.new([
105
+ InnerPlan::SmartArray::Item.new(:created_by, { span: 12 }, created_by_item)
106
+ ]))
107
+ ])
108
+
109
+ @task_row_addons = InnerPlan::SmartArray.new([
110
+ ::InnerPlan::SmartArray::Item.new(:group, {}, 'InnerPlan::Tasks::Row::GroupAddonComponent'),
111
+ ::InnerPlan::SmartArray::Item.new(:description, {}, 'InnerPlan::Tasks::Row::DescriptionAddonComponent'),
112
+ ::InnerPlan::SmartArray::Item.new(:due_on, {}, 'InnerPlan::Tasks::Row::DueOnAddonComponent'),
113
+ ])
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,22 @@
1
+ module InnerPlan
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace InnerPlan
4
+
5
+ initializer "inner_plan.importmap", before: "importmap" do |app|
6
+ # https://github.com/rails/importmap-rails#composing-import-maps
7
+ # app.config.importmap.paths << root.join("config/importmap.rb")
8
+ InnerPlan.importmap.draw(root.join("config/importmap.rb"))
9
+
10
+ # https://github.com/rails/importmap-rails#sweeping-the-cache-in-development-and-test
11
+ app.config.importmap.cache_sweepers << root.join("app/javascript")
12
+ end
13
+
14
+ initializer "inner_plan.assets" do |app|
15
+ # my_engine/app/javascript needs to be in the asset path
16
+ app.config.assets.paths << root.join("app/javascript")
17
+
18
+ # manifest has to be precompiled
19
+ app.config.assets.precompile += %w[inner_plan/manifest.js]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,66 @@
1
+ module InnerPlan
2
+ class SmartArray < Array
3
+ class Item < Struct.new(:key, :options, :content); end
4
+
5
+ class KeyNotFoundError < StandardError; end
6
+ class WrongItemTypeError < StandardError; end
7
+ class ItemKeyNotUniqueError < StandardError; end
8
+
9
+ def add(item)
10
+ raise WrongItemTypeError unless item.is_a?(Item)
11
+ raise ItemKeyNotUniqueError if has_item_key?(item.key)
12
+
13
+ self.push(item)
14
+ end
15
+
16
+ def add_before(before_key, item)
17
+ raise WrongItemTypeError unless item.is_a?(Item)
18
+ raise ItemKeyNotUniqueError if has_item_key?(item.key)
19
+
20
+ index = find_item_index(before_key)
21
+ raise KeyNotFoundError unless index
22
+
23
+ self.insert(index, item)
24
+ end
25
+
26
+ def add_after(after_key, item)
27
+ raise WrongItemTypeError unless item.is_a?(Item)
28
+ raise ItemKeyNotUniqueError if has_item_key?(item.key)
29
+
30
+ index = find_item_index(after_key)
31
+ raise KeyNotFoundError unless index
32
+
33
+ self.insert(index + 1, item)
34
+ end
35
+
36
+ def replace(key, item)
37
+ raise WrongItemTypeError unless item.is_a?(Item)
38
+
39
+ index = find_item_index(key)
40
+ raise KeyNotFoundError unless index
41
+
42
+ self[index] = item
43
+
44
+ self
45
+ end
46
+
47
+ def get(key)
48
+ index = find_item_index(key)
49
+ raise KeyNotFoundError unless index
50
+
51
+ self[index]
52
+ end
53
+
54
+ private
55
+
56
+ def find_item_index(key)
57
+ self.find_index do |item|
58
+ item.key == key
59
+ end
60
+ end
61
+
62
+ def has_item_key?(key)
63
+ find_item_index(key) != nil
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ module InnerPlan
2
+ VERSION = "0.1.0"
3
+ end
data/lib/inner_plan.rb ADDED
@@ -0,0 +1,28 @@
1
+ require "inner_plan/version"
2
+ require "inner_plan/engine"
3
+ require "inner_plan/configuration"
4
+ require "inner_plan/smart_array"
5
+
6
+ require "turbo-rails"
7
+ require "positioning"
8
+ require "importmap-rails"
9
+ require "image_processing"
10
+ require "phlex"
11
+ require "phlex-rails"
12
+ require "phlex-icons-tabler"
13
+ require "trailblazer-rails"
14
+ require "stringex"
15
+
16
+ module InnerPlan
17
+ class << self
18
+ def configuration
19
+ @configuration ||= Configuration.new
20
+ end
21
+
22
+ def configure
23
+ yield(configuration)
24
+ end
25
+ end
26
+
27
+ mattr_accessor :importmap, default: Importmap::Map.new
28
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :inner_plan do
3
+ # # Task goes here
4
+ # end