satis 1.0.66

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 (101) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +92 -0
  4. data/Rakefile +23 -0
  5. data/app/assets/config/satis_manifest.js +1 -0
  6. data/app/assets/stylesheets/satis/application.css +15 -0
  7. data/app/components/satis/appearance_switcher/component.html.slim +6 -0
  8. data/app/components/satis/appearance_switcher/component.rb +11 -0
  9. data/app/components/satis/appearance_switcher/component.scss +34 -0
  10. data/app/components/satis/appearance_switcher/component_controller.js +62 -0
  11. data/app/components/satis/application_component.rb +50 -0
  12. data/app/components/satis/avatar/component.html.slim +7 -0
  13. data/app/components/satis/avatar/component.rb +52 -0
  14. data/app/components/satis/breadcrumbs/component.html.slim +8 -0
  15. data/app/components/satis/breadcrumbs/component.rb +23 -0
  16. data/app/components/satis/breadcrumbs/component.scss +19 -0
  17. data/app/components/satis/breadcrumbs/crumb.slim +8 -0
  18. data/app/components/satis/card/component.html.slim +54 -0
  19. data/app/components/satis/card/component.md +14 -0
  20. data/app/components/satis/card/component.rb +41 -0
  21. data/app/components/satis/card/component.scss +15 -0
  22. data/app/components/satis/date_time_picker/component.html.slim +48 -0
  23. data/app/components/satis/date_time_picker/component.md +11 -0
  24. data/app/components/satis/date_time_picker/component.rb +48 -0
  25. data/app/components/satis/date_time_picker/component.scss +5 -0
  26. data/app/components/satis/date_time_picker/component_controller.js +499 -0
  27. data/app/components/satis/dropdown/component.html.slim +36 -0
  28. data/app/components/satis/dropdown/component.md +48 -0
  29. data/app/components/satis/dropdown/component.rb +77 -0
  30. data/app/components/satis/dropdown/component.scss +10 -0
  31. data/app/components/satis/dropdown/component_controller.js +547 -0
  32. data/app/components/satis/flash_messages/component.html.slim +3 -0
  33. data/app/components/satis/flash_messages/component.rb +31 -0
  34. data/app/components/satis/flash_messages/component.scss +18 -0
  35. data/app/components/satis/flash_messages/message.html.slim +8 -0
  36. data/app/components/satis/info/component.html.slim +4 -0
  37. data/app/components/satis/info/component.rb +22 -0
  38. data/app/components/satis/info_item/component.html.slim +7 -0
  39. data/app/components/satis/info_item/component.rb +19 -0
  40. data/app/components/satis/input/component.html.slim +11 -0
  41. data/app/components/satis/input/component.rb +38 -0
  42. data/app/components/satis/input/component.scss +50 -0
  43. data/app/components/satis/input/element.html.slim +2 -0
  44. data/app/components/satis/map/component.html.slim +2 -0
  45. data/app/components/satis/map/component.rb +17 -0
  46. data/app/components/satis/map/component.scss +9 -0
  47. data/app/components/satis/map/component_controller.js +37 -0
  48. data/app/components/satis/menu/component.html.slim +13 -0
  49. data/app/components/satis/menu/component.md +1 -0
  50. data/app/components/satis/menu/component.rb +16 -0
  51. data/app/components/satis/menu/component_controller.js +62 -0
  52. data/app/components/satis/menu_item/component.html.slim +16 -0
  53. data/app/components/satis/menu_item/component.rb +14 -0
  54. data/app/components/satis/page/component.html.slim +45 -0
  55. data/app/components/satis/page/component.rb +15 -0
  56. data/app/components/satis/page/component_controller.js +86 -0
  57. data/app/components/satis/sidebar_menu/component.html.slim +3 -0
  58. data/app/components/satis/sidebar_menu/component.rb +17 -0
  59. data/app/components/satis/sidebar_menu/component.scss +0 -0
  60. data/app/components/satis/sidebar_menu/component_controller.js +9 -0
  61. data/app/components/satis/sidebar_menu/mobile/component.html.slim +3 -0
  62. data/app/components/satis/sidebar_menu/mobile/component.rb +10 -0
  63. data/app/components/satis/sidebar_menu_item/component.html.slim +15 -0
  64. data/app/components/satis/sidebar_menu_item/component.rb +20 -0
  65. data/app/components/satis/sidebar_menu_item/component.scss +27 -0
  66. data/app/components/satis/sidebar_menu_item/component_controller.js +62 -0
  67. data/app/components/satis/sidebar_menu_item/mobile/component.html.slim +17 -0
  68. data/app/components/satis/sidebar_menu_item/mobile/component.rb +10 -0
  69. data/app/components/satis/switch/component.html.slim +14 -0
  70. data/app/components/satis/switch/component.rb +24 -0
  71. data/app/components/satis/switch/component_controller.js +49 -0
  72. data/app/components/satis/tab/component.rb +35 -0
  73. data/app/components/satis/tabs/component.html.slim +23 -0
  74. data/app/components/satis/tabs/component.md +21 -0
  75. data/app/components/satis/tabs/component.rb +16 -0
  76. data/app/components/satis/tabs/component.scss +33 -0
  77. data/app/components/satis/tabs/component_controller.js +123 -0
  78. data/app/controllers/satis/application_controller.rb +4 -0
  79. data/app/helpers/satis/application_helper.rb +15 -0
  80. data/app/jobs/satis/application_job.rb +4 -0
  81. data/app/mailers/satis/application_mailer.rb +6 -0
  82. data/app/models/satis/application_record.rb +5 -0
  83. data/app/views/shared/_fields_for.html.slim +35 -0
  84. data/config/routes.rb +5 -0
  85. data/lib/satis/action_controller_helpers.rb +29 -0
  86. data/lib/satis/configuration.rb +61 -0
  87. data/lib/satis/engine.rb +27 -0
  88. data/lib/satis/forms/builder.rb +440 -0
  89. data/lib/satis/forms/concerns/buttons.rb +49 -0
  90. data/lib/satis/forms/concerns/file.rb +35 -0
  91. data/lib/satis/forms/concerns/options.rb +44 -0
  92. data/lib/satis/forms/concerns/required.rb +68 -0
  93. data/lib/satis/forms/concerns/select.rb +95 -0
  94. data/lib/satis/helpers/container.rb +83 -0
  95. data/lib/satis/menus/builder.rb +13 -0
  96. data/lib/satis/menus/item.rb +34 -0
  97. data/lib/satis/menus/menu.rb +23 -0
  98. data/lib/satis/version.rb +3 -0
  99. data/lib/satis.rb +36 -0
  100. data/lib/tasks/satis_tasks.rake +4 -0
  101. metadata +213 -0
@@ -0,0 +1,62 @@
1
+ import ApplicationController from "../../../../frontend/controllers/application_controller"
2
+ // FIXME: Is this full path really needed?
3
+ import { debounce } from "../../../../frontend/utils"
4
+
5
+ export default class extends ApplicationController {
6
+ static targets = ["link", "indicator", "submenu"]
7
+
8
+ connect() {
9
+ super.connect()
10
+
11
+ // Primitive, yes
12
+ Array.from(this.element.querySelectorAll('[data-satis-sidebar-menu-item-target="link"]')).forEach((el) => {
13
+ if (el.href.length > 0 && window.location.href.indexOf(el.href) >= 0) {
14
+ el.classList.add("active")
15
+ }
16
+ })
17
+
18
+ if (this.isActive) {
19
+ this.linkTarget.classList.add("active")
20
+
21
+ if (this.hasSubmenuTarget) {
22
+ this.submenuTarget.classList.remove("hidden")
23
+ this.indicatorTarget.setAttribute("data-fa-transform", "rotate-90")
24
+ } else {
25
+ this.linkTarget.classList.add("focus")
26
+ }
27
+ }
28
+ }
29
+
30
+ open(event) {
31
+ if (!this.isActive && this.hasSubmenuTarget) {
32
+ if (this.hasSubmenuTarget) {
33
+ this.submenuTarget.classList.remove("hidden")
34
+ this.indicatorTarget.setAttribute("data-fa-transform", "rotate-90")
35
+ }
36
+ event.preventDefault()
37
+ }
38
+ }
39
+
40
+ get linkInUrl() {
41
+ return this.linkTarget.href.length > 0 && window.location.href.indexOf(this.linkTarget.href) >= 0
42
+ }
43
+
44
+ get isActive() {
45
+ return this.linkInUrl || this.hasOpenSubmenus || this.hasActiveLinks
46
+ }
47
+
48
+ get hasOpenSubmenus() {
49
+ return Array.from(this.element.querySelectorAll('[data-satis-sidebar-menu-item-target="submenu"]')).some((el) => {
50
+ return !el.classList.contains("hidden")
51
+ // return Array.from(el.querySelectorAll('[data-satis-sidebar-menu-item-target="submenu"]')).some((el) => {
52
+ // return !el.classList.contains("hidden")
53
+ // })
54
+ })
55
+ }
56
+
57
+ get hasActiveLinks() {
58
+ return Array.from(this.element.querySelectorAll('[data-satis-sidebar-menu-item-target="link"]')).some((el) => {
59
+ return el.classList.contains("active")
60
+ })
61
+ }
62
+ }
@@ -0,0 +1,17 @@
1
+ .space-y-1 class="#{item.level >= 1 ? 'pl-4' : ''}"
2
+ a.text-gray-600.hover:bg-gray-50.hover:text-gray-900.group.flex.items-center.px-2.py-2.text-base.font-medium.rounded-md href=item.link *item.link_attributes
3
+ - if item.icon
4
+ i.fa-lg.text-gray-400.group-hover:text-gray-500.mr-4.flex-shrink-0.h-6.w-6 class=item.icon style="width: 20px;"
5
+ - else
6
+ i.mr-3.flex-shrink-0.h-6 style="width: 20px;"
7
+ span.flex-1
8
+ = item.label
9
+
10
+ /! Expanded: "text-gray-400 rotate-90", Collapsed: "text-gray-300"
11
+ - if item.menu
12
+ i.fal.fa-angle-right.text-gray-300.ml-3.flex-shrink-0.h-5.w-5.transform.group-hover:text-gray-400.transition-colors.ease-in-out.duration-150 style="width: 20px;"
13
+
14
+ - if item.menu
15
+ #sub-menu-1.space-y-1
16
+ - item.menu.items.each do |item|
17
+ = render(Satis::SidebarMenuItem::Mobile::Component.new(item: item))
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Satis
4
+ module SidebarMenuItem
5
+ module Mobile
6
+ class Component < ::Satis::SidebarMenuItem::Component
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ div.satis-switch data-controller='satis-switch'
2
+ - if options[:label] != false
3
+ = form.custom_label(attribute, options[:label], data: { action: "click->satis-switch#toggle" })
4
+ = form.hidden_field(attribute, options[:input_html].reverse_merge(value: @value ? "1" : "0", 'data-action' => 'change->satis-switch#update'))
5
+ button.mt-3.mb-3.relative.inline-flex.flex-shrink-0.h-6.w-11.border-2.border-transparent.rounded-full.cursor-pointer.transition-colors.ease-in-out.duration-200.focus:outline-none.focus:ring-2.focus:ring-offset-2.focus:ring-primary-500 aria-checked="false" role="switch" type="button" data-action="click->satis-switch#toggle" data-satis-switch-target="button" class="#{@value ? 'bg-primary-600' : 'bg-gray-200'}"
6
+ span.pointer-events-none.inline-block.h-5.w-5.rounded-full.bg-white.shadow.transform.ring-0.transition.ease-in-out.duration-200 aria-hidden="true" data-satis-switch-target="switch" class="#{@value ? 'translate-x-5' : 'translate-x-0' }"
7
+ - if icon
8
+ span.absolute.inset-0.h-full.w-full.flex.items-center.justify-center.transition-opacity aria-hidden="true" data-satis-switch-target="cross" class="#{@value ? 'opacity-0 ease-out duration-100' : 'opacity-100 ease-in duration-200' }"
9
+ svg.h-3.w-3.text-gray-400 fill="none" viewbox=("0 0 12 12")
10
+ path d=("M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2") stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" /
11
+ span.absolute.inset-0.h-full.w-full.flex.items-center.justify-center.transition-opacity aria-hidden="true" data-satis-switch-target="check" class="#{@value ? 'opacity-100 ease-in duration-200' : 'opacity-0 ease-out duration-100' }"
12
+ svg.h-3.w-3.text-primary-600 fill="currentColor" viewbox=("0 0 12 12")
13
+ path d=("M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z") /
14
+
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Satis
4
+ module Switch
5
+ class Component < Satis::ApplicationComponent
6
+ attr_reader :url, :form, :attribute, :icon, :options
7
+
8
+ def initialize(form:, attribute:, **options, &block)
9
+ super
10
+
11
+ @form = form
12
+ @attribute = attribute
13
+ @options = options
14
+ @block = block
15
+ @icon = true
16
+ @icon = options[:icon] if options.key?(:icon)
17
+ @value = options.key?(:value) ? options[:value] : @form.object.send(attribute)
18
+ @value = @value == '0' || !@value ? false : true
19
+ options[:input_html] ||= {}
20
+ options[:input_html] = { data: { 'satis-switch-target' => 'hiddenInput' } }.deep_merge(options[:input_html])
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ import ApplicationController from "../../../../frontend/controllers/application_controller"
2
+ // FIXME: Is this full path really needed?
3
+ import { debounce } from "../../../../frontend/utils"
4
+
5
+ export default class extends ApplicationController {
6
+ static targets = ["hiddenInput", "switch", "button", "cross", "check"]
7
+
8
+ connect() {
9
+ super.connect()
10
+ }
11
+
12
+ toggle(event) {
13
+ this.hiddenInputTarget.value = this.hiddenInputTarget.value == "1" ? "0" : "1"
14
+ this.hiddenInputTarget.dispatchEvent(new Event("change"))
15
+ this.update()
16
+ }
17
+
18
+ update(event) {
19
+ if (this.hiddenInputTarget.value == "1") {
20
+ // enabled
21
+ this.buttonTarget.classList.add("bg-primary-600")
22
+ this.buttonTarget.classList.remove("bg-gray-200")
23
+ this.switchTarget.classList.add("translate-x-5")
24
+ this.switchTarget.classList.remove("translate-x-0")
25
+
26
+ if (this.hasCrossTarget && this.hasCheckTarget) {
27
+ this.crossTarget.classList.add("opacity-0", "ease-out", "duration-100")
28
+ this.checkTarget.classList.add("opacity-100", "ease-in", "duration-200")
29
+
30
+ this.crossTarget.classList.remove("opacity-100", "ease-in", "duration-200")
31
+ this.checkTarget.classList.remove("opacity-0", "ease-out", "duration-100")
32
+ }
33
+ } else {
34
+ // disabled
35
+ this.buttonTarget.classList.remove("bg-primary-600")
36
+ this.buttonTarget.classList.add("bg-gray-200")
37
+ this.switchTarget.classList.remove("translate-x-5")
38
+ this.switchTarget.classList.add("translate-x-0")
39
+
40
+ if (this.hasCrossTarget && this.hasCheckTarget) {
41
+ this.crossTarget.classList.remove("opacity-0", "ease-out", "duration-100")
42
+ this.checkTarget.classList.remove("opacity-100", "ease-in", "duration-200")
43
+
44
+ this.crossTarget.classList.add("opacity-100", "ease-in", "duration-200")
45
+ this.checkTarget.classList.add("opacity-0", "ease-out", "duration-100")
46
+ }
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Satis
4
+ module Tab
5
+ class Component < Satis::ApplicationComponent
6
+ attr_reader :options, :name, :icon, :badge
7
+
8
+ def initialize(name, *args, &block)
9
+ super
10
+ @name = name
11
+ @options = args.extract_options!
12
+ @args = args
13
+ @icon = options[:icon]
14
+ @badge = options[:badge]
15
+ @block = block
16
+ end
17
+
18
+ def responsive?
19
+ options[:responsive] == true
20
+ end
21
+
22
+ def selected?
23
+ options[:selected] == true
24
+ end
25
+
26
+ def title
27
+ options[:title]
28
+ end
29
+
30
+ def call
31
+ content
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ .sts-tabs id="#{group}" data-controller="satis-tabs" data-satis-tabs-persist-value="#{persist}"
2
+ .sm:hidden
3
+ label.sr-only for="tabs" Select a tab
4
+ select#tabs.block.w-full.pl-3.pr-10.py-2.text-base.border-gray-300.focus:outline-none.focus:ring-primary-500.focus:border-primary-500.sm:text-sm.rounded-md name="tabs" data-action="change->satis-tabs#select" data-satis-tabs-target="select"
5
+ - tabs.each do |tab|
6
+ option selected=tab.selected? = ct(".#{tab.name}", scope: [group.to_sym], default: tab.name.to_s.humanize)
7
+ .hidden.sm:block
8
+ .border-b.border-gray-200
9
+ nav.-mb-px.flex.space-x-8.overflow-x-auto aria-label="Tabs"
10
+ - tabs.each do |tab|
11
+ a.tab id="#{tab.name}" href="#" aria-current="#{tab.selected? ? "page" : ''}" data-satis-tabs-target="tab" data-action="click->satis-tabs#select"
12
+ - if tab.icon
13
+ i.mr-2 class=tab.icon
14
+ = ct(".#{tab.name}", scope: [group.to_sym], default: tab.name.to_s.humanize)
15
+ i.fal.fa-triangle-exclamation.ml-2.hidden
16
+ - if tab.badge
17
+ span.badge
18
+ = tab.badge
19
+
20
+ div
21
+ - tabs.each do |tab|
22
+ div id="#{tab.name}-content" class="tab-content #{tab.options[:padding] == false ? '' : 'mt-4'}" data-satis-tabs-target="content"
23
+ = tab.to_s
@@ -0,0 +1,21 @@
1
+ # Tabs
2
+
3
+ ## UI
4
+
5
+ https://tailwindui.com/components/application-ui/navigation/tabs
6
+
7
+ ## Usage
8
+
9
+ ```slim
10
+ = sts.tabs do |t|
11
+ - t.tab :about
12
+ p About
13
+ = link_to "Hello", root_path
14
+ - t.tab :printers
15
+ | printers
16
+ - t.tab :preferences
17
+ | preferences
18
+ ```
19
+
20
+ satis.tabs takes an optional "group" parameter and is :main by default. So you could do:
21
+ `satis.tabs group: :second`
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Satis
4
+ module Tabs
5
+ class Component < Satis::ApplicationComponent
6
+ renders_many :tabs, Tab::Component
7
+ attr_reader :group, :persist
8
+
9
+ def initialize(group: :main, persist: false)
10
+ super
11
+ @group = group
12
+ @persist = persist
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,33 @@
1
+ .sts-tabs {
2
+ .tab {
3
+ @apply border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap pb-4 px-1 border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300;
4
+
5
+ .badge {
6
+ @apply bg-gray-100 text-gray-600 hidden ml-3 rounded-full text-xs font-medium md:inline-block py-0.5 px-2.5;
7
+ }
8
+
9
+ &.selected {
10
+ @apply border-primary-500 text-primary-600;
11
+
12
+ .badge {
13
+ @apply bg-primary-100 text-primary-600;
14
+ }
15
+ }
16
+
17
+ &.is-invalid {
18
+ @apply text-red-600;
19
+
20
+ .fa-triangle-exclamation {
21
+ display: inline;
22
+ }
23
+ }
24
+ }
25
+
26
+ .tab-content {
27
+ @apply hidden;
28
+
29
+ &.selected {
30
+ @apply block;
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,123 @@
1
+ import ApplicationController from "../../../../frontend/controllers/application_controller"
2
+
3
+ /*
4
+ * Tabs controller
5
+ */
6
+ export default class extends ApplicationController {
7
+ static targets = ["tab", "content", "select"]
8
+ static values = { persist: Boolean }
9
+
10
+ static keyBindings = [
11
+ {
12
+ keys: ["ctrl+1", "ctrl+2", "ctrl+3", "ctrl+4", "ctrl+5", "ctrl+6", "ctrl+7", "ctrl+8", "ctrl+9", "ctrl+0"],
13
+ handler: (event, combo, controller) => {
14
+ let index = -1 + +combo.split("+")[1]
15
+ if (index == -1) {
16
+ index = 10
17
+ }
18
+ controller.open(index)
19
+ },
20
+ },
21
+ ]
22
+
23
+ connect() {
24
+ super.connect()
25
+
26
+ const ourUrl = new URL(window.location.href)
27
+ this.keyBase = ourUrl.pathname.substring(1, ourUrl.pathname.length).replace(/\//, "_") + "_tabs_" + this.context.scope.element.id
28
+
29
+ let firstErrorIndex
30
+ this.tabTargets.forEach((tab, index) => {
31
+ let hasErrors = this.contentTargets[index].querySelectorAll(".is-invalid")
32
+ if (hasErrors.length > 0) {
33
+ if (!firstErrorIndex) {
34
+ firstErrorIndex = index
35
+ }
36
+ tab.classList.add("is-invalid")
37
+ }
38
+ })
39
+
40
+ this.open(firstErrorIndex || this.tabToOpen())
41
+ }
42
+
43
+ select(event) {
44
+ let index = null
45
+ if (event.srcElement.tagName == "SELECT") {
46
+ index = event.srcElement.selectedIndex
47
+ } else {
48
+ let clickedTab = event.srcElement.closest("a")
49
+ index = this.tabTargets.findIndex((el) => {
50
+ return el.attributes["id"] === clickedTab.attributes["id"]
51
+ })
52
+ }
53
+ this.open(index)
54
+ this.storeValue("openTab", index)
55
+
56
+ // Cancel the this event (dont show the browser context menu)
57
+ event.preventDefault()
58
+ return false
59
+ }
60
+
61
+ open(index) {
62
+ if (index == -1 || this.tabTargets[index] === undefined) {
63
+ return
64
+ }
65
+
66
+ this.tabTargets.forEach(function (target) {
67
+ target.classList.remove("selected")
68
+ })
69
+ this.tabTargets[index].classList.add("selected")
70
+
71
+ this.contentTargets.forEach(function (target) {
72
+ target.classList.remove("selected")
73
+ })
74
+ this.contentTargets[index].classList.add("selected")
75
+ this.selectTarget.selectedIndex = index
76
+ }
77
+
78
+ storeValue(key, value) {
79
+ if (!this.persistValue) {
80
+ return
81
+ }
82
+
83
+ if (typeof Storage !== "undefined") {
84
+ sessionStorage.setItem(this.keyBase + "_" + key, value)
85
+ }
86
+ }
87
+
88
+ getValue(key) {
89
+ if (!this.persistValue) {
90
+ return
91
+ }
92
+
93
+ if (typeof Storage !== "undefined") {
94
+ return sessionStorage.getItem(this.keyBase + "_" + key)
95
+ }
96
+ }
97
+
98
+ disconnect() {}
99
+
100
+ tabToOpen() {
101
+ let urlValue = this.getUrlVar(this.context.scope.element.id + "Tab")
102
+
103
+ if (typeof urlValue !== "undefined") {
104
+ return urlValue
105
+ }
106
+
107
+ return this.getValue("openTab") || 0
108
+ }
109
+
110
+ getUrlVar(name) {
111
+ return this.getUrlVars()[name]
112
+ }
113
+
114
+ getUrlVars() {
115
+ let vars = {}
116
+
117
+ window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) {
118
+ vars[key] = value
119
+ })
120
+
121
+ return vars
122
+ }
123
+ }
@@ -0,0 +1,4 @@
1
+ module Satis
2
+ class ApplicationController < ::ApplicationController
3
+ end
4
+ end
@@ -0,0 +1,15 @@
1
+ module Satis
2
+ module ApplicationHelper
3
+ def sts
4
+ @_satis_helpers_container ||= Satis::Helpers::Container.new(self)
5
+ end
6
+
7
+ def method_missing(method, *args, **kwargs, &block)
8
+ if method.to_s.ends_with?('_url') || method.to_s.ends_with?('_path') && main_app.respond_to?(method)
9
+ main_app.send(method, *args, **kwargs, &block)
10
+ else
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ module Satis
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Satis
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Satis
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,35 @@
1
+ - if form.object.present? && form.object.errors.messages[collection].present?
2
+ div.invalid-feedback
3
+ = form.object.errors.full_messages_for(collection).join(', ')
4
+
5
+ - if template_object
6
+ template data-satis-fields-for-target='template'
7
+ = form.rails_fields_for collection, template_object, child_index: 'TEMPLATE' do |nested_form|
8
+ .nested-fields.template.py-2
9
+ = nested_form.input :id, as: :hidden
10
+ = nested_form.input :_destroy, as: :hidden
11
+ .grid.grid-cols-12.gap-4
12
+ .col-span-11.fields
13
+ = yield(nested_form)
14
+ .col-span-1.flex.justify-center.items-center.association
15
+ .h-full.w-1.border-r.border-dashed
16
+ a.text-primary-600.bg-white.dark:bg-gray-800 href="#" data-action='click->satis-fields-for#addAssociation' style="margin-left: -7px;"
17
+ i.fal.fa-plus
18
+ .hidden.col-span-1.flex.justify-center.items-center.association
19
+ .h-full.w-1.border-r.border-dashed
20
+ a.text-primary-600.bg-white.dark:bg-gray-800 href="#" data-action='click->satis-fields-for#removeAssociation' style="margin-left: -7px;"
21
+ i.fal.fa-trash
22
+
23
+ = form.rails_fields_for collection do |nested_form|
24
+ .nested-fields.py-2
25
+ = nested_form.input :id, as: :hidden
26
+ = nested_form.input :_destroy, as: :hidden
27
+ .grid.grid-cols-12.gap-4
28
+ .col-span-11
29
+ = yield(nested_form)
30
+ .col-span-1.flex.justify-center.items-center
31
+ .h-full.w-1.border-r.border-dashed
32
+ a.text-primary-600.bg-white.dark:bg-gray-800 href="#" data-action='click->satis-fields-for#removeAssociation' style="margin-left: -7px;"
33
+ i.fal.fa-trash
34
+
35
+ span data-satis-fields-for-target='insertionPoint'
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ Satis::Engine.routes.draw do
2
+ resources :tables, param: :table_name do
3
+ get 'filter_collection', on: :collection
4
+ end
5
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Satis
4
+ class StsWrapper
5
+ attr_reader :request
6
+
7
+ def initialize(request)
8
+ @request = request
9
+ end
10
+
11
+ def browser
12
+ @browser ||= Browser.new(request.user_agent)
13
+ end
14
+ end
15
+
16
+ module ActionControllerHelpers
17
+ extend ActiveSupport::Concern
18
+
19
+ included do
20
+ def sts
21
+ StsWrapper.new(request)
22
+ end
23
+ end
24
+
25
+ class_methods do
26
+ # Nothing yet
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Satis
4
+ class Configuration
5
+ attr_accessor :submit_on_enter, :confirm_before_leave
6
+ attr_writer :default_help_text
7
+ attr_writer :logger
8
+
9
+ def initialize
10
+ @logger = Logger.new(STDOUT)
11
+ @submit_on_enter = true
12
+ @confirm_before_leave = false
13
+
14
+ @default_help_text = lambda do |_template, _object, key, _additional_scope|
15
+ scope = help_scope(template, object, additional_scope)
16
+
17
+ value = I18n.t((["help"] + scope + [key.to_s]).join("."))
18
+
19
+ if /translation missing: (.+)/.match?(value)
20
+ nil
21
+ else
22
+ value
23
+ end
24
+ end
25
+ end
26
+
27
+ # Config: logger [Object].
28
+ def logger
29
+ @logger.is_a?(Proc) ? instance_exec(&@logger) : @logger
30
+ end
31
+
32
+ def default_help_text(template, object, method, additional_scope)
33
+ if @default_help_text.is_a?(Proc)
34
+ instance_exec(template, object, method, additional_scope,
35
+ &@default_help_text)
36
+ else
37
+ @default_help_text
38
+ end
39
+ end
40
+
41
+ # Maybe not the right place?
42
+ def help_scope(template, object, additional_scope, action: nil)
43
+ scope = template.controller.controller_path.split("/")
44
+ scope << (action || template.controller.action_name)
45
+ scope << object.class.name.demodulize.tableize.singularize
46
+
47
+ scope += Array.wrap(additional_scope) if additional_scope
48
+
49
+ scope.map(&:to_s)
50
+ end
51
+
52
+ def help_scopes(template, object, additional_scope)
53
+ actions = [template.controller.action_name]
54
+ %w[show new edit create update destroy index].each do |action|
55
+ actions << action unless actions.include?(action)
56
+ end
57
+
58
+ actions.map { |action| help_scope(template, object, additional_scope, action: action) }
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,27 @@
1
+ require 'satis/forms/builder'
2
+ require 'satis/helpers/container'
3
+ require 'satis/menus/builder'
4
+
5
+ module Satis
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace Satis
8
+
9
+ config.autoload_paths << "#{root}/app/components"
10
+ config.autoload_paths << "#{root}/lib"
11
+
12
+ initializer 'satis.helper' do
13
+ Rails.application.reloader.to_prepare do
14
+ ActiveSupport.on_load :action_view do
15
+ include Satis::ApplicationHelper
16
+
17
+ # F*CK Rails, adding an extra surrounding div 'field_with_errors' breaking all the things.
18
+ self.field_error_proc = ->(html_tag, _instance) { html_tag }
19
+ end
20
+
21
+ ActiveSupport.on_load(:action_controller) do
22
+ include Satis::ActionControllerHelpers
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end