smriti 0.5.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +168 -0
- data/Rakefile +15 -0
- data/app/assets/images/smriti/android-chrome-192x192.png +0 -0
- data/app/assets/images/smriti/android-chrome-512x512.png +0 -0
- data/app/assets/images/smriti/apple-touch-icon.png +0 -0
- data/app/assets/images/smriti/favicon-16x16.png +0 -0
- data/app/assets/images/smriti/favicon-32x32.png +0 -0
- data/app/assets/images/smriti/favicon-48x48.png +0 -0
- data/app/assets/images/smriti/favicon.ico +0 -0
- data/app/assets/images/smriti/favicon.svg +18 -0
- data/app/assets/images/smriti/logo.svg +18 -0
- data/app/assets/images/smriti/mask-icon.svg +5 -0
- data/app/assets/stylesheets/smriti/application.css +1040 -0
- data/app/controllers/smriti/admin/application_controller.rb +135 -0
- data/app/controllers/smriti/admin/dashboard_controller.rb +32 -0
- data/app/controllers/smriti/admin/mat_view_definitions_controller.rb +372 -0
- data/app/controllers/smriti/admin/mat_view_runs_controller.rb +185 -0
- data/app/controllers/smriti/admin/preferences_controller.rb +91 -0
- data/app/helpers/smriti/admin/datatable_helper.rb +249 -0
- data/app/helpers/smriti/admin/localized_digit_helper.rb +70 -0
- data/app/helpers/smriti/admin/ui_helper.rb +539 -0
- data/app/javascript/smriti/application.js +8 -0
- data/app/javascript/smriti/controllers/application.js +10 -0
- data/app/javascript/smriti/controllers/body_setup_controller.js +120 -0
- data/app/javascript/smriti/controllers/datatable_controller.js +351 -0
- data/app/javascript/smriti/controllers/details_controller.js +200 -0
- data/app/javascript/smriti/controllers/drawer_controller.js +470 -0
- data/app/javascript/smriti/controllers/flash_controller.js +112 -0
- data/app/javascript/smriti/controllers/index.js +10 -0
- data/app/javascript/smriti/controllers/mv_confirm_controller.js +435 -0
- data/app/javascript/smriti/controllers/tabs_controller.js +184 -0
- data/app/javascript/smriti/controllers/tooltip_controller.js +525 -0
- data/app/javascript/smriti/controllers/turbo_frame_lifecycle_controller.js +342 -0
- data/app/jobs/smriti/application_job.rb +144 -0
- data/app/jobs/smriti/create_view_job.rb +87 -0
- data/app/jobs/smriti/delete_view_job.rb +89 -0
- data/app/jobs/smriti/refresh_view_job.rb +94 -0
- data/app/models/concerns/smriti_i18n.rb +139 -0
- data/app/models/concerns/smriti_paginate.rb +70 -0
- data/app/models/concerns/smriti_query_helper.rb +36 -0
- data/app/models/smriti/application_record.rb +39 -0
- data/app/models/smriti/mat_view_definition.rb +254 -0
- data/app/models/smriti/mat_view_run.rb +275 -0
- data/app/views/layouts/smriti/_footer.html.erb +47 -0
- data/app/views/layouts/smriti/_header.html.erb +25 -0
- data/app/views/layouts/smriti/admin.html.erb +47 -0
- data/app/views/layouts/smriti/turbo_frame.html.erb +3 -0
- data/app/views/smriti/admin/dashboard/index.html.erb +38 -0
- data/app/views/smriti/admin/mat_view_definitions/_definition_actions.html.erb +94 -0
- data/app/views/smriti/admin/mat_view_definitions/_dt-index-empty-row.html.erb +11 -0
- data/app/views/smriti/admin/mat_view_definitions/_dt-index-row.html.erb +27 -0
- data/app/views/smriti/admin/mat_view_definitions/empty.html.erb +1 -0
- data/app/views/smriti/admin/mat_view_definitions/form.html.erb +79 -0
- data/app/views/smriti/admin/mat_view_definitions/index.html.erb +10 -0
- data/app/views/smriti/admin/mat_view_definitions/show.html.erb +40 -0
- data/app/views/smriti/admin/mat_view_runs/_dt-index-empty-row.html.erb +11 -0
- data/app/views/smriti/admin/mat_view_runs/_dt-index-row.html.erb +41 -0
- data/app/views/smriti/admin/mat_view_runs/index.html.erb +1 -0
- data/app/views/smriti/admin/mat_view_runs/show.html.erb +64 -0
- data/app/views/smriti/admin/preferences/show.html.erb +49 -0
- data/app/views/smriti/admin/ui/_card.html.erb +15 -0
- data/app/views/smriti/admin/ui/_datatable.html.erb +34 -0
- data/app/views/smriti/admin/ui/_datatable_filters.html.erb +45 -0
- data/app/views/smriti/admin/ui/_datatable_tbody.html.erb +11 -0
- data/app/views/smriti/admin/ui/_datatable_tfoot.html.erb +70 -0
- data/app/views/smriti/admin/ui/_datatable_thead.html.erb +105 -0
- data/app/views/smriti/admin/ui/_details.html.erb +10 -0
- data/app/views/smriti/admin/ui/_flash.html.erb +6 -0
- data/app/views/smriti/admin/ui/_table.html.erb +8 -0
- data/config/importmap.rb +9 -0
- data/config/locales/ar.yml +223 -0
- data/config/locales/de.yml +230 -0
- data/config/locales/en-AU-ocker.yml +223 -0
- data/config/locales/en-AU.yml +202 -0
- data/config/locales/en-BORK.yml +225 -0
- data/config/locales/en-CA.yml +223 -0
- data/config/locales/en-GB.yml +223 -0
- data/config/locales/en-LOL.yml +219 -0
- data/config/locales/en-SCOT.yml +223 -0
- data/config/locales/en-SHAKESPEARE.yml +225 -0
- data/config/locales/en-US-pirate.yml +222 -0
- data/config/locales/en-US.yml +225 -0
- data/config/locales/en-YODA.yml +221 -0
- data/config/locales/en.yml +223 -0
- data/config/locales/es.yml +226 -0
- data/config/locales/fa.yml +223 -0
- data/config/locales/fr-CA.yml +227 -0
- data/config/locales/fr.yml +227 -0
- data/config/locales/he.yml +218 -0
- data/config/locales/hi.yml +223 -0
- data/config/locales/it.yml +225 -0
- data/config/locales/ja-JP.yml +215 -0
- data/config/locales/pt.yml +225 -0
- data/config/locales/ru.yml +228 -0
- data/config/locales/ur.yml +225 -0
- data/config/locales/zh-CN.yml +214 -0
- data/config/locales/zh-TW.yml +214 -0
- data/config/routes.rb +36 -0
- data/lib/ext/exception.rb +20 -0
- data/lib/generators/smriti/install/install_generator.rb +86 -0
- data/lib/generators/smriti/install/templates/create_mat_view_definitions.rb +29 -0
- data/lib/generators/smriti/install/templates/create_mat_view_runs.rb +32 -0
- data/lib/generators/smriti/install/templates/smriti_initializer.rb +23 -0
- data/lib/smriti/admin/auth_bridge.rb +93 -0
- data/lib/smriti/admin/default_auth.rb +62 -0
- data/lib/smriti/configuration.rb +58 -0
- data/lib/smriti/engine.rb +82 -0
- data/lib/smriti/helpers/ui_test_ids.rb +49 -0
- data/lib/smriti/jobs/adapter.rb +81 -0
- data/lib/smriti/service_response.rb +75 -0
- data/lib/smriti/services/base_service.rb +471 -0
- data/lib/smriti/services/check_matview_exists.rb +76 -0
- data/lib/smriti/services/concurrent_refresh.rb +94 -0
- data/lib/smriti/services/create_view.rb +173 -0
- data/lib/smriti/services/delete_view.rb +111 -0
- data/lib/smriti/services/regular_refresh.rb +90 -0
- data/lib/smriti/services/swap_refresh.rb +181 -0
- data/lib/smriti/version.rb +21 -0
- data/lib/smriti.rb +64 -0
- data/lib/tasks/helpers.rb +185 -0
- data/lib/tasks/smriti_tasks.rake +151 -0
- metadata +206 -0
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Codevedas Inc. 2025-present
|
|
4
|
+
#
|
|
5
|
+
# This source code is licensed under the MIT license found in the
|
|
6
|
+
# LICENSE file in the root directory of this source tree.
|
|
7
|
+
|
|
8
|
+
module Smriti
|
|
9
|
+
module Admin
|
|
10
|
+
# Smriti::Admin::UiHelper
|
|
11
|
+
# -------------------------
|
|
12
|
+
# View helper methods for the Smriti admin UI.
|
|
13
|
+
#
|
|
14
|
+
# Responsibilities:
|
|
15
|
+
# - Provides consistent button, link, drawer, badge, and icon components.
|
|
16
|
+
# - Wraps standard Rails helpers (`link_to`, `button_to`, `button_tag`, etc.)
|
|
17
|
+
# with Smriti-specific styling and Stimulus integration.
|
|
18
|
+
# - Defines inline SVG icon snippets for use across the admin dashboard.
|
|
19
|
+
#
|
|
20
|
+
# Key Components:
|
|
21
|
+
# - Buttons: {#mv_button_link}, {#mv_button_to}, {#mv_drawer_link}, {#mv_drawer_action_button}
|
|
22
|
+
# - Links: {#mv_link_to}
|
|
23
|
+
# - Badges: {#mv_badge}
|
|
24
|
+
# - Icons: {#mv_icon} with private `svg_icon_*` methods
|
|
25
|
+
#
|
|
26
|
+
module UiHelper
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# Builds CSS classes for a Smriti-styled button.
|
|
30
|
+
#
|
|
31
|
+
# @api private
|
|
32
|
+
#
|
|
33
|
+
# @param variant [Symbol] one of `:primary`, `:secondary`, `:ghost`, `:negative`
|
|
34
|
+
# @param size [Symbol] one of `:sm`, `:md`, `:lg`
|
|
35
|
+
# @return [String] concatenated CSS class string
|
|
36
|
+
def mv_button_classes(variant, size)
|
|
37
|
+
[
|
|
38
|
+
'mv-btn',
|
|
39
|
+
"mv-btn--#{variant}",
|
|
40
|
+
"mv-btn--#{size}"
|
|
41
|
+
].join(' ')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Renders a styled link button.
|
|
45
|
+
#
|
|
46
|
+
# @api private
|
|
47
|
+
#
|
|
48
|
+
# @param href [String] target URL
|
|
49
|
+
# @param opts [Hash] options for styling, data attributes, etc.
|
|
50
|
+
# @yield link body content
|
|
51
|
+
# @return [String] HTML-safe link tag
|
|
52
|
+
def mv_button_link(href, opts = {}, &)
|
|
53
|
+
link_to capture(&), href, **link_options(assign_test_id(opts))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Renders a button link that opens a drawer via Stimulus.
|
|
57
|
+
#
|
|
58
|
+
# @api private
|
|
59
|
+
#
|
|
60
|
+
# @param drawer_url [String] URL to load into the drawer
|
|
61
|
+
# @param drawer_title [String] title for the drawer
|
|
62
|
+
# @param args [Hash] additional options
|
|
63
|
+
# @yield button body
|
|
64
|
+
# @return [String] HTML-safe link tag
|
|
65
|
+
def mv_drawer_link(drawer_url, drawer_title, args = {}, &)
|
|
66
|
+
data = { action: 'click->drawer#open', drawer_title: drawer_title, drawer_url: drawer_url }
|
|
67
|
+
args[:data] = (args[:data] || {}).merge(data)
|
|
68
|
+
mv_button_link '#', assign_test_id(args), &
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Renders a styled `button_to` element.
|
|
72
|
+
#
|
|
73
|
+
# @api private
|
|
74
|
+
#
|
|
75
|
+
# @param href [String] target URL
|
|
76
|
+
# @param opts [Hash] options for styling, data attributes, etc.
|
|
77
|
+
# @yield button content
|
|
78
|
+
# @return [String] HTML-safe button tag
|
|
79
|
+
def mv_button_to(href, opts = {}, &)
|
|
80
|
+
button_to href, **link_options(assign_test_id(opts)) do
|
|
81
|
+
capture(&)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Renders a drawer action button with optional tooltip.
|
|
86
|
+
#
|
|
87
|
+
# @api private
|
|
88
|
+
#
|
|
89
|
+
# @param label [String] ARIA label for accessibility
|
|
90
|
+
# @param action [String] Stimulus action method
|
|
91
|
+
# @param tooltip [String, nil] optional tooltip text
|
|
92
|
+
# @param tooltip_placement [String, nil] placement of tooltip (default: "top")
|
|
93
|
+
# @param args_orig [Hash] additional HTML options
|
|
94
|
+
# @yield button content
|
|
95
|
+
# @return [String] HTML-safe button tag
|
|
96
|
+
def mv_drawer_action_button(label, action, tooltip = nil, tooltip_placement = nil, args_org = {}, &)
|
|
97
|
+
args = assign_test_id(args_org)
|
|
98
|
+
data = { action: "drawer##{action}" }
|
|
99
|
+
data = data.merge(args[:data] || {})
|
|
100
|
+
if tooltip
|
|
101
|
+
data[:controller] = 'tooltip'
|
|
102
|
+
data[:'tooltip-text-value'] = tooltip
|
|
103
|
+
data[:'tooltip-placement'] = tooltip_placement || 'top'
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
args[:data] = data
|
|
107
|
+
|
|
108
|
+
button_tag(type: 'button', class: 'mv-drawer-action', 'aria-label': label, **args, &)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Renders a styled external or internal link, with optional tooltip.
|
|
112
|
+
#
|
|
113
|
+
# @api private
|
|
114
|
+
#
|
|
115
|
+
# @param text [String, nil] link text (nil if using block form)
|
|
116
|
+
# @param url [String, nil] target URL
|
|
117
|
+
# @param args_orig [Hash] HTML options (supports `:tooltip`, `:is_blank`)
|
|
118
|
+
# @yield link body when block form is used
|
|
119
|
+
# @return [String] HTML-safe link tag
|
|
120
|
+
def mv_link_to(text = nil, url = nil, args_orig = nil, &block)
|
|
121
|
+
args = assign_test_id(args_orig || {})
|
|
122
|
+
if block_given?
|
|
123
|
+
args = assign_test_id(url || {})
|
|
124
|
+
url = text
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
tooltip = args.fetch(:tooltip, nil)
|
|
128
|
+
is_blank = args.fetch(:is_blank, true)
|
|
129
|
+
underline = args.fetch(:underline, true)
|
|
130
|
+
args_to_apply = args.except(:tooltip, :is_blank)
|
|
131
|
+
if is_blank
|
|
132
|
+
args_to_apply[:target] = '_blank'
|
|
133
|
+
args_to_apply[:rel] = 'noopener noreferrer'
|
|
134
|
+
end
|
|
135
|
+
args_to_apply[:class] = 'underline' if underline
|
|
136
|
+
if tooltip
|
|
137
|
+
args_to_apply[:'data-controller'] = 'tooltip'
|
|
138
|
+
args_to_apply[:'data-tooltip-text-value'] = tooltip
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if block_given?
|
|
142
|
+
link_to url, args_to_apply do
|
|
143
|
+
capture(&block)
|
|
144
|
+
end
|
|
145
|
+
else
|
|
146
|
+
link_to text, url, **args_to_apply
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Renders a tab link for the tabs component.
|
|
151
|
+
#
|
|
152
|
+
# @api private
|
|
153
|
+
#
|
|
154
|
+
# @param tab_name [String] unique name of the tab
|
|
155
|
+
# @param args_org [Hash] additional HTML options
|
|
156
|
+
#
|
|
157
|
+
# @yield tab link content
|
|
158
|
+
#
|
|
159
|
+
# @return [String] HTML-safe link tag
|
|
160
|
+
def mv_tab_link(tab_name, args_org = {}, &)
|
|
161
|
+
args = assign_test_id(args_org)
|
|
162
|
+
classes = ['mv-tab']
|
|
163
|
+
selected = args.delete(:selected)
|
|
164
|
+
classes << (selected ? 'mv-tab--on' : '')
|
|
165
|
+
args[:class] = [args[:class], classes.compact.join(' ')].compact.join(' ').strip
|
|
166
|
+
args[:data] = args.fetch(:data, {}).merge({ action: 'click->tabs#show', 'tabs-target': 'link', name: tab_name })
|
|
167
|
+
link_to '#', **args, &
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Renders a badge element styled by status.
|
|
171
|
+
#
|
|
172
|
+
# @api private
|
|
173
|
+
#
|
|
174
|
+
# @param status [String, Symbol] status name (`success`, `running`, `failed`, etc.)
|
|
175
|
+
# @param text [String] text to display inside the badge
|
|
176
|
+
# @return [String] HTML-safe span tag with badge classes
|
|
177
|
+
def mv_badge(status, text)
|
|
178
|
+
klass = case status.to_s.downcase
|
|
179
|
+
when 'success' then 'mv-chip mv-chip--success'
|
|
180
|
+
when 'running' then 'mv-chip mv-chip--running'
|
|
181
|
+
when 'failed' then 'mv-chip mv-chip--failed'
|
|
182
|
+
else 'mv-chip'
|
|
183
|
+
end
|
|
184
|
+
content_tag(:span, text, class: klass)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Renders an inline SVG icon.
|
|
188
|
+
#
|
|
189
|
+
# @api private
|
|
190
|
+
#
|
|
191
|
+
# @param name [String, Symbol] icon name (method suffix after `svg_icon_`)
|
|
192
|
+
# @param size [Integer] icon width/height in pixels
|
|
193
|
+
# @param class_name [String, nil] optional extra CSS class
|
|
194
|
+
# @return [String] HTML-safe SVG element
|
|
195
|
+
def mv_icon(name, size: 16, class_name: nil)
|
|
196
|
+
icon_method_name = :"svg_icon_#{name}"
|
|
197
|
+
content_tag :svg,
|
|
198
|
+
class: ['mv-icon', class_name].compact.join(' '),
|
|
199
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
200
|
+
width: size, height: size, viewBox: '0 0 24 24',
|
|
201
|
+
fill: 'none', stroke: 'currentColor',
|
|
202
|
+
'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round',
|
|
203
|
+
'aria-hidden': 'true' do
|
|
204
|
+
respond_to?(icon_method_name, true) ? raw(send(icon_method_name)) : ''.html_safe
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Renders a styled submit button.
|
|
209
|
+
#
|
|
210
|
+
# @api private
|
|
211
|
+
#
|
|
212
|
+
# @param opts [Hash] options for styling, data attributes, etc.
|
|
213
|
+
# @yield button content
|
|
214
|
+
# @return [String] HTML-safe button tag
|
|
215
|
+
def mv_submit_button(opts = {}, &)
|
|
216
|
+
opts = link_options(assign_test_id(opts))
|
|
217
|
+
opts[:type] = 'submit'
|
|
218
|
+
opts.delete(:method)
|
|
219
|
+
button_tag(capture(&), **opts)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Renders a styled generic button.
|
|
223
|
+
#
|
|
224
|
+
# @api private
|
|
225
|
+
#
|
|
226
|
+
# @param opts [Hash] options for styling, data attributes, etc.
|
|
227
|
+
# @yield button content
|
|
228
|
+
# @return [String] HTML-safe button tag
|
|
229
|
+
def mv_button(opts = {}, &)
|
|
230
|
+
opts = link_options(assign_test_id(opts))
|
|
231
|
+
opts[:type] = 'button'
|
|
232
|
+
opts.delete(:method)
|
|
233
|
+
button_tag(capture(&), **opts)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Renders a styled cancel button.
|
|
237
|
+
#
|
|
238
|
+
# @api private
|
|
239
|
+
#
|
|
240
|
+
# @param opts [Hash] options for styling, data attributes, etc.
|
|
241
|
+
# @yield button content
|
|
242
|
+
# @return [String] HTML-safe button tag
|
|
243
|
+
def mv_cancel_button(opts = {}, &)
|
|
244
|
+
mv_button(opts.merge(variant: :secondary), &)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Builds standardized options hash for button/link helpers.
|
|
248
|
+
#
|
|
249
|
+
# @api private
|
|
250
|
+
#
|
|
251
|
+
# @param opts [Hash] options including :variant, :size, :method, :tooltip, etc.
|
|
252
|
+
# @return [Hash] merged HTML attributes (class, method, data)
|
|
253
|
+
def link_options(opts)
|
|
254
|
+
variant = opts.fetch(:variant, :primary)
|
|
255
|
+
size = opts.fetch(:size, :md)
|
|
256
|
+
classes = opts[:class] ? " #{opts.delete(:class)}" : ''
|
|
257
|
+
method = opts.fetch(:method, :get)
|
|
258
|
+
disabled = opts.fetch(:disabled, false)
|
|
259
|
+
underline = opts[:underline] ? ' underline' : ''
|
|
260
|
+
|
|
261
|
+
tip = opts[:tooltip]
|
|
262
|
+
tooltip = if tip
|
|
263
|
+
{
|
|
264
|
+
controller: 'tooltip',
|
|
265
|
+
'tooltip-text-value': tip,
|
|
266
|
+
'tooltip-placement': opts.fetch(:tooltip_placement, 'top')
|
|
267
|
+
}
|
|
268
|
+
else
|
|
269
|
+
{}
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
html_data = (opts[:data] || {}).dup.merge(tooltip)
|
|
273
|
+
html_data[:'turbo-confirm'] = opts.fetch(:confirm, nil)
|
|
274
|
+
{ class: "#{mv_button_classes(variant, size)}#{underline}#{classes}", method: method, disabled: disabled, data: html_data }
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# SVG markup for a right-pointing arrow icon
|
|
278
|
+
#
|
|
279
|
+
# @api private
|
|
280
|
+
#
|
|
281
|
+
# @return [String] SVG markup
|
|
282
|
+
def svg_icon_arrow_right
|
|
283
|
+
<<~SVG.strip.freeze
|
|
284
|
+
<polyline points="9 18 15 12 9 6"/>
|
|
285
|
+
SVG
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# SVG markup for a left-pointing arrow icon
|
|
289
|
+
#
|
|
290
|
+
# @api private
|
|
291
|
+
#
|
|
292
|
+
# @return [String] SVG markup
|
|
293
|
+
def svg_icon_arrow_left
|
|
294
|
+
<<~SVG.strip.freeze
|
|
295
|
+
<polyline points="15 18 9 12 15 6"/>
|
|
296
|
+
SVG
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# SVG markup for a left-pointing double-arrow icon
|
|
300
|
+
#
|
|
301
|
+
# @api private
|
|
302
|
+
#
|
|
303
|
+
# @return [String] SVG markup
|
|
304
|
+
def svg_icon_double_arrow_left
|
|
305
|
+
<<~SVG.strip.freeze
|
|
306
|
+
<polyline points="11 17 6 12 11 7"/>
|
|
307
|
+
<polyline points="18 17 13 12 18 7"/>
|
|
308
|
+
SVG
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# SVG markup for a right-pointing double-arrow icon
|
|
312
|
+
#
|
|
313
|
+
# @api private
|
|
314
|
+
#
|
|
315
|
+
# @return [String] SVG markup
|
|
316
|
+
def svg_icon_double_arrow_right
|
|
317
|
+
<<~SVG.strip.freeze
|
|
318
|
+
<polyline points="13 17 18 12 13 7"/>
|
|
319
|
+
<polyline points="6 17 11 12 6 7"/>
|
|
320
|
+
SVG
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# SVG markup for a refresh/reload icon
|
|
324
|
+
#
|
|
325
|
+
# @api private
|
|
326
|
+
#
|
|
327
|
+
# @return [String] SVG markup
|
|
328
|
+
def svg_icon_refresh
|
|
329
|
+
<<~SVG.strip.freeze
|
|
330
|
+
<path d="M21 12a9 9 0 1 1-2.64-6.36"/>
|
|
331
|
+
<polyline points="23 4 23 10 17 10"/>
|
|
332
|
+
SVG
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# SVG markup for a trash/delete (bin) icon
|
|
336
|
+
#
|
|
337
|
+
# @api private
|
|
338
|
+
#
|
|
339
|
+
# @return [String] SVG markup
|
|
340
|
+
def svg_icon_trash
|
|
341
|
+
<<~SVG.strip.freeze
|
|
342
|
+
<polyline points="3 6 5 6 21 6"/>
|
|
343
|
+
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
|
|
344
|
+
<path d="M10 11v6"/>
|
|
345
|
+
<path d="M14 11v6"/>
|
|
346
|
+
<path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"/>
|
|
347
|
+
SVG
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# SVG markup for a hammer/tool icon
|
|
351
|
+
#
|
|
352
|
+
# @api private
|
|
353
|
+
#
|
|
354
|
+
# @return [String] SVG markup
|
|
355
|
+
def svg_icon_hammer
|
|
356
|
+
<<~SVG.strip.freeze
|
|
357
|
+
<path d="M14 4l7 7"/>
|
|
358
|
+
<path d="M5 15l7-7 3 3-7 7H5v-3z"/>
|
|
359
|
+
SVG
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# SVG markup for an “X in a circle” (close/error) icon
|
|
363
|
+
#
|
|
364
|
+
# @api private
|
|
365
|
+
#
|
|
366
|
+
# @return [String] SVG markup
|
|
367
|
+
def svg_icon_x_circle
|
|
368
|
+
<<~SVG.strip.freeze
|
|
369
|
+
<circle cx="12" cy="12" r="10"/>
|
|
370
|
+
<path d="M15 9l-6 6"/>
|
|
371
|
+
<path d="M9 9l6 6"/>
|
|
372
|
+
SVG
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# SVG markup for a “plus in a circle” (add) icon
|
|
376
|
+
#
|
|
377
|
+
# @api private
|
|
378
|
+
#
|
|
379
|
+
# @return [String] SVG markup
|
|
380
|
+
def svg_icon_plus_circle
|
|
381
|
+
<<~SVG.strip.freeze
|
|
382
|
+
<circle cx="12" cy="12" r="10"/>
|
|
383
|
+
<line x1="12" y1="8" x2="12" y2="16"/>
|
|
384
|
+
<line x1="8" y1="12" x2="16" y2="12"/>
|
|
385
|
+
SVG
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# SVG markup for a checkmark-in-circle (success) icon
|
|
389
|
+
#
|
|
390
|
+
# @api private
|
|
391
|
+
#
|
|
392
|
+
# @return [String] SVG markup
|
|
393
|
+
def svg_icon_check_circle
|
|
394
|
+
<<~SVG.strip.freeze
|
|
395
|
+
<circle cx="12" cy="12" r="10"/>
|
|
396
|
+
<polyline points="9 12 12 15 16 9"/>
|
|
397
|
+
SVG
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# SVG markup for an alert/warning triangle icon
|
|
401
|
+
#
|
|
402
|
+
# @api private
|
|
403
|
+
#
|
|
404
|
+
# @return [String] SVG markup
|
|
405
|
+
def svg_icon_alert
|
|
406
|
+
<<~SVG.strip.freeze
|
|
407
|
+
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
|
|
408
|
+
<line x1="12" y1="9" x2="12" y2="13"/>
|
|
409
|
+
<line x1="12" y1="17" x2="12.01" y2="17"/>
|
|
410
|
+
SVG
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# SVG markup for a history/clock icon
|
|
414
|
+
#
|
|
415
|
+
# @api private
|
|
416
|
+
#
|
|
417
|
+
# @return [String] SVG markup
|
|
418
|
+
def svg_icon_history
|
|
419
|
+
<<~SVG.strip.freeze
|
|
420
|
+
<path d="M3 3v5h5"/>
|
|
421
|
+
<path d="M3.05 13a9 9 0 1 0 .5-5.5"/>
|
|
422
|
+
<path d="M12 7v5l3 3"/>
|
|
423
|
+
SVG
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# SVG markup for an edit/pencil icon
|
|
427
|
+
#
|
|
428
|
+
# @api private
|
|
429
|
+
#
|
|
430
|
+
# @return [String] SVG markup
|
|
431
|
+
def svg_icon_edit
|
|
432
|
+
<<~SVG.strip.freeze
|
|
433
|
+
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
434
|
+
<path d="M18.5 2.5a2.1 2.1 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
435
|
+
SVG
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# SVG markup for a stacked-layers icon
|
|
439
|
+
#
|
|
440
|
+
# @api private
|
|
441
|
+
#
|
|
442
|
+
# @return [String] SVG markup
|
|
443
|
+
def svg_icon_layers
|
|
444
|
+
<<~SVG.strip.freeze
|
|
445
|
+
<polygon points="12 2 2 7 12 12 22 7 12 2"/>
|
|
446
|
+
<polyline points="2 17 12 22 22 17"/>
|
|
447
|
+
<polyline points="2 12 12 17 22 12"/>
|
|
448
|
+
SVG
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# SVG markup for a database/cylinder icon
|
|
452
|
+
#
|
|
453
|
+
# @api private
|
|
454
|
+
#
|
|
455
|
+
# @return [String] SVG markup
|
|
456
|
+
def svg_icon_database
|
|
457
|
+
<<~SVG.strip.freeze
|
|
458
|
+
<ellipse cx="12" cy="5" rx="9" ry="3"/>
|
|
459
|
+
<path d="M3 5v6c0 1.66 4.03 3 9 3s9-1.34 9-3V5"/>
|
|
460
|
+
<path d="M3 11v6c0 1.66 4.03 3 9 3s9-1.34 9-3v-6"/>
|
|
461
|
+
SVG
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# SVG markup for a gear/settings icon
|
|
465
|
+
#
|
|
466
|
+
# @api private
|
|
467
|
+
#
|
|
468
|
+
# @return [String] SVG markup
|
|
469
|
+
def svg_icon_gear
|
|
470
|
+
<<~SVG.strip.freeze
|
|
471
|
+
<circle cx="12" cy="12" r="3"></circle>
|
|
472
|
+
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
|
473
|
+
SVG
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# SVG markup for a sort ascending icon
|
|
477
|
+
#
|
|
478
|
+
# @api private
|
|
479
|
+
#
|
|
480
|
+
# @return [String] SVG markup
|
|
481
|
+
def svg_icon_sort_asc
|
|
482
|
+
<<~SVG.strip.freeze
|
|
483
|
+
<line x1="6" y1="6" x2="12" y2="6"/>
|
|
484
|
+
<line x1="6" y1="12" x2="16" y2="12"/>
|
|
485
|
+
<line x1="6" y1="18" x2="20" y2="18"/>
|
|
486
|
+
SVG
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
# SVG markup for a sort descending icon
|
|
490
|
+
#
|
|
491
|
+
# @api private
|
|
492
|
+
#
|
|
493
|
+
# @return [String] SVG markup
|
|
494
|
+
def svg_icon_sort_desc
|
|
495
|
+
<<~SVG.strip.freeze
|
|
496
|
+
<line x1="6" y1="6" x2="20" y2="6"/>
|
|
497
|
+
<line x1="6" y1="12" x2="16" y2="12"/>
|
|
498
|
+
<line x1="6" y1="18" x2="12" y2="18"/>
|
|
499
|
+
SVG
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
# SVG markup for a neutral sort icon (no particular order)
|
|
503
|
+
#
|
|
504
|
+
# @api private
|
|
505
|
+
#
|
|
506
|
+
# @return [String] SVG markup
|
|
507
|
+
def svg_icon_sort_neutral
|
|
508
|
+
# three lines: top line is greater, middle is lower then top, bottom is lower then middle
|
|
509
|
+
<<~SVG.strip.freeze
|
|
510
|
+
<line x1="6" y1="6" x2="20" y2="6"/>
|
|
511
|
+
<line x1="6" y1="12" x2="20" y2="12"/>
|
|
512
|
+
<line x1="6" y1="18" x2="20" y2="18"/>
|
|
513
|
+
SVG
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Maps a symbolic test ID to its actual string value for data attributes.
|
|
517
|
+
# if `:testid` key is not present, returns original args unchanged.
|
|
518
|
+
#
|
|
519
|
+
# @api private
|
|
520
|
+
#
|
|
521
|
+
# @param args [Hash] original options hash
|
|
522
|
+
# @return [Hash] modified options hash with `data-testid` set
|
|
523
|
+
#
|
|
524
|
+
# @example
|
|
525
|
+
# assign_test_id(class: 'btn', testid: :HEADER_LINK)
|
|
526
|
+
# # => { class: 'btn', data: { testid: 'header_link' } }
|
|
527
|
+
#
|
|
528
|
+
def assign_test_id(args = {})
|
|
529
|
+
return args unless args[:testid].present?
|
|
530
|
+
|
|
531
|
+
testid_constant = args.delete(:testid)
|
|
532
|
+
testid_identifier = args.delete(:testid_identifier) || ''
|
|
533
|
+
args_data = args[:data] || {}
|
|
534
|
+
args_data[:testid] = "#{Smriti::Helpers::UiTestIds.const_get(testid_constant)}-#{testid_identifier}".chomp('-')
|
|
535
|
+
args.merge data: args_data
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Codevedas Inc. 2025-present
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import { Application } from "@hotwired/stimulus";
|
|
8
|
+
const application = Application.start();
|
|
9
|
+
window.Stimulus = application;
|
|
10
|
+
export { application };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Codevedas Inc. 2025-present
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Stimulus Controller: ThemeAndTimezoneController
|
|
10
|
+
* -----------------------------------------------
|
|
11
|
+
* Manages browser timezone cookie and applied theme attributes.
|
|
12
|
+
*
|
|
13
|
+
* Responsibilities:
|
|
14
|
+
* - Detects and stores the browser’s timezone in a cookie.
|
|
15
|
+
* - Determines and applies the appropriate UI theme (light/dark)
|
|
16
|
+
* based on user setting or system preference.
|
|
17
|
+
* - Ensures consistency between `data-theme` and `data-applied-theme`
|
|
18
|
+
* attributes on the `<html>` element.
|
|
19
|
+
*
|
|
20
|
+
* Key Components:
|
|
21
|
+
* - Timezone: `_ensureTimezoneCookie`, `_cookieMatches`, `_writeCookie`
|
|
22
|
+
* - Theme: `_ensureAppliedTheme`, `_applyTheme`
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { Controller } from "@hotwired/stimulus";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @class ThemeAndTimezoneController
|
|
29
|
+
* @extends Controller
|
|
30
|
+
*/
|
|
31
|
+
export default class extends Controller {
|
|
32
|
+
/**
|
|
33
|
+
* Static values configuration for Stimulus values API.
|
|
34
|
+
* @property {string} timezoneCookie - Name of the timezone cookie.
|
|
35
|
+
* @property {string} timezoneCookiePath - Path for the timezone cookie.
|
|
36
|
+
* @property {string} themeAttribute - Attribute containing desired theme.
|
|
37
|
+
* @property {string} appliedThemeAttribute - Attribute to store applied theme.
|
|
38
|
+
*/
|
|
39
|
+
static values = {
|
|
40
|
+
timezoneCookie: { type: String, default: "browser_tz" },
|
|
41
|
+
timezoneCookiePath: { type: String, default: "/" },
|
|
42
|
+
themeAttribute: { type: String, default: "data-theme" },
|
|
43
|
+
appliedThemeAttribute: { type: String, default: "data-applied-theme" },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Initializes controller-level references.
|
|
48
|
+
*/
|
|
49
|
+
initialize() {
|
|
50
|
+
/** @type {HTMLElement} Root HTML element */
|
|
51
|
+
this.htmlElement = document.documentElement;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Called when the controller is connected to the DOM.
|
|
56
|
+
* Ensures timezone and theme are properly set.
|
|
57
|
+
*/
|
|
58
|
+
connect() {
|
|
59
|
+
this._ensureTimezoneCookie();
|
|
60
|
+
this._ensureAppliedTheme();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Timezone ─────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Ensures that a cookie storing the browser's timezone is set.
|
|
67
|
+
*/
|
|
68
|
+
_ensureTimezoneCookie() {
|
|
69
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
70
|
+
if (this._cookieMatches(tz)) return;
|
|
71
|
+
this._writeCookie(tz);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Checks whether the stored cookie already matches the given value.
|
|
76
|
+
* @param {string} value - Timezone string to check.
|
|
77
|
+
* @return {boolean} True if cookie already matches.
|
|
78
|
+
*/
|
|
79
|
+
_cookieMatches(value) {
|
|
80
|
+
const needle = `${this.timezoneCookieValue}=${value}`;
|
|
81
|
+
return document.cookie.split(";").some((entry) => entry.trim() === needle);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Writes a cookie with the provided timezone value.
|
|
86
|
+
* @param {string} value - Timezone value (e.g., "America/New_York").
|
|
87
|
+
* @return {void}
|
|
88
|
+
*/
|
|
89
|
+
_writeCookie(value) {
|
|
90
|
+
document.cookie = `${this.timezoneCookieValue}=${value}; path=${this.timezoneCookiePathValue}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Theme ────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Ensures that the applied theme matches either a user preference
|
|
97
|
+
* or the system’s color-scheme setting.
|
|
98
|
+
*/
|
|
99
|
+
_ensureAppliedTheme() {
|
|
100
|
+
const setting = this.htmlElement.getAttribute(this.themeAttributeValue);
|
|
101
|
+
if (setting === "light" || setting === "dark") {
|
|
102
|
+
this._applyTheme(setting);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const prefersDark = window.matchMedia(
|
|
107
|
+
"(prefers-color-scheme: dark)",
|
|
108
|
+
).matches;
|
|
109
|
+
this._applyTheme(prefersDark ? "dark" : "light");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Applies a theme by updating the `data-applied-theme` attribute.
|
|
114
|
+
* @param {"light" | "dark"} theme - Theme name to apply.
|
|
115
|
+
* @return {void}
|
|
116
|
+
*/
|
|
117
|
+
_applyTheme(theme) {
|
|
118
|
+
this.htmlElement.setAttribute(this.appliedThemeAttributeValue, theme);
|
|
119
|
+
}
|
|
120
|
+
}
|