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.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +168 -0
  4. data/Rakefile +15 -0
  5. data/app/assets/images/smriti/android-chrome-192x192.png +0 -0
  6. data/app/assets/images/smriti/android-chrome-512x512.png +0 -0
  7. data/app/assets/images/smriti/apple-touch-icon.png +0 -0
  8. data/app/assets/images/smriti/favicon-16x16.png +0 -0
  9. data/app/assets/images/smriti/favicon-32x32.png +0 -0
  10. data/app/assets/images/smriti/favicon-48x48.png +0 -0
  11. data/app/assets/images/smriti/favicon.ico +0 -0
  12. data/app/assets/images/smriti/favicon.svg +18 -0
  13. data/app/assets/images/smriti/logo.svg +18 -0
  14. data/app/assets/images/smriti/mask-icon.svg +5 -0
  15. data/app/assets/stylesheets/smriti/application.css +1040 -0
  16. data/app/controllers/smriti/admin/application_controller.rb +135 -0
  17. data/app/controllers/smriti/admin/dashboard_controller.rb +32 -0
  18. data/app/controllers/smriti/admin/mat_view_definitions_controller.rb +372 -0
  19. data/app/controllers/smriti/admin/mat_view_runs_controller.rb +185 -0
  20. data/app/controllers/smriti/admin/preferences_controller.rb +91 -0
  21. data/app/helpers/smriti/admin/datatable_helper.rb +249 -0
  22. data/app/helpers/smriti/admin/localized_digit_helper.rb +70 -0
  23. data/app/helpers/smriti/admin/ui_helper.rb +539 -0
  24. data/app/javascript/smriti/application.js +8 -0
  25. data/app/javascript/smriti/controllers/application.js +10 -0
  26. data/app/javascript/smriti/controllers/body_setup_controller.js +120 -0
  27. data/app/javascript/smriti/controllers/datatable_controller.js +351 -0
  28. data/app/javascript/smriti/controllers/details_controller.js +200 -0
  29. data/app/javascript/smriti/controllers/drawer_controller.js +470 -0
  30. data/app/javascript/smriti/controllers/flash_controller.js +112 -0
  31. data/app/javascript/smriti/controllers/index.js +10 -0
  32. data/app/javascript/smriti/controllers/mv_confirm_controller.js +435 -0
  33. data/app/javascript/smriti/controllers/tabs_controller.js +184 -0
  34. data/app/javascript/smriti/controllers/tooltip_controller.js +525 -0
  35. data/app/javascript/smriti/controllers/turbo_frame_lifecycle_controller.js +342 -0
  36. data/app/jobs/smriti/application_job.rb +144 -0
  37. data/app/jobs/smriti/create_view_job.rb +87 -0
  38. data/app/jobs/smriti/delete_view_job.rb +89 -0
  39. data/app/jobs/smriti/refresh_view_job.rb +94 -0
  40. data/app/models/concerns/smriti_i18n.rb +139 -0
  41. data/app/models/concerns/smriti_paginate.rb +70 -0
  42. data/app/models/concerns/smriti_query_helper.rb +36 -0
  43. data/app/models/smriti/application_record.rb +39 -0
  44. data/app/models/smriti/mat_view_definition.rb +254 -0
  45. data/app/models/smriti/mat_view_run.rb +275 -0
  46. data/app/views/layouts/smriti/_footer.html.erb +47 -0
  47. data/app/views/layouts/smriti/_header.html.erb +25 -0
  48. data/app/views/layouts/smriti/admin.html.erb +47 -0
  49. data/app/views/layouts/smriti/turbo_frame.html.erb +3 -0
  50. data/app/views/smriti/admin/dashboard/index.html.erb +38 -0
  51. data/app/views/smriti/admin/mat_view_definitions/_definition_actions.html.erb +94 -0
  52. data/app/views/smriti/admin/mat_view_definitions/_dt-index-empty-row.html.erb +11 -0
  53. data/app/views/smriti/admin/mat_view_definitions/_dt-index-row.html.erb +27 -0
  54. data/app/views/smriti/admin/mat_view_definitions/empty.html.erb +1 -0
  55. data/app/views/smriti/admin/mat_view_definitions/form.html.erb +79 -0
  56. data/app/views/smriti/admin/mat_view_definitions/index.html.erb +10 -0
  57. data/app/views/smriti/admin/mat_view_definitions/show.html.erb +40 -0
  58. data/app/views/smriti/admin/mat_view_runs/_dt-index-empty-row.html.erb +11 -0
  59. data/app/views/smriti/admin/mat_view_runs/_dt-index-row.html.erb +41 -0
  60. data/app/views/smriti/admin/mat_view_runs/index.html.erb +1 -0
  61. data/app/views/smriti/admin/mat_view_runs/show.html.erb +64 -0
  62. data/app/views/smriti/admin/preferences/show.html.erb +49 -0
  63. data/app/views/smriti/admin/ui/_card.html.erb +15 -0
  64. data/app/views/smriti/admin/ui/_datatable.html.erb +34 -0
  65. data/app/views/smriti/admin/ui/_datatable_filters.html.erb +45 -0
  66. data/app/views/smriti/admin/ui/_datatable_tbody.html.erb +11 -0
  67. data/app/views/smriti/admin/ui/_datatable_tfoot.html.erb +70 -0
  68. data/app/views/smriti/admin/ui/_datatable_thead.html.erb +105 -0
  69. data/app/views/smriti/admin/ui/_details.html.erb +10 -0
  70. data/app/views/smriti/admin/ui/_flash.html.erb +6 -0
  71. data/app/views/smriti/admin/ui/_table.html.erb +8 -0
  72. data/config/importmap.rb +9 -0
  73. data/config/locales/ar.yml +223 -0
  74. data/config/locales/de.yml +230 -0
  75. data/config/locales/en-AU-ocker.yml +223 -0
  76. data/config/locales/en-AU.yml +202 -0
  77. data/config/locales/en-BORK.yml +225 -0
  78. data/config/locales/en-CA.yml +223 -0
  79. data/config/locales/en-GB.yml +223 -0
  80. data/config/locales/en-LOL.yml +219 -0
  81. data/config/locales/en-SCOT.yml +223 -0
  82. data/config/locales/en-SHAKESPEARE.yml +225 -0
  83. data/config/locales/en-US-pirate.yml +222 -0
  84. data/config/locales/en-US.yml +225 -0
  85. data/config/locales/en-YODA.yml +221 -0
  86. data/config/locales/en.yml +223 -0
  87. data/config/locales/es.yml +226 -0
  88. data/config/locales/fa.yml +223 -0
  89. data/config/locales/fr-CA.yml +227 -0
  90. data/config/locales/fr.yml +227 -0
  91. data/config/locales/he.yml +218 -0
  92. data/config/locales/hi.yml +223 -0
  93. data/config/locales/it.yml +225 -0
  94. data/config/locales/ja-JP.yml +215 -0
  95. data/config/locales/pt.yml +225 -0
  96. data/config/locales/ru.yml +228 -0
  97. data/config/locales/ur.yml +225 -0
  98. data/config/locales/zh-CN.yml +214 -0
  99. data/config/locales/zh-TW.yml +214 -0
  100. data/config/routes.rb +36 -0
  101. data/lib/ext/exception.rb +20 -0
  102. data/lib/generators/smriti/install/install_generator.rb +86 -0
  103. data/lib/generators/smriti/install/templates/create_mat_view_definitions.rb +29 -0
  104. data/lib/generators/smriti/install/templates/create_mat_view_runs.rb +32 -0
  105. data/lib/generators/smriti/install/templates/smriti_initializer.rb +23 -0
  106. data/lib/smriti/admin/auth_bridge.rb +93 -0
  107. data/lib/smriti/admin/default_auth.rb +62 -0
  108. data/lib/smriti/configuration.rb +58 -0
  109. data/lib/smriti/engine.rb +82 -0
  110. data/lib/smriti/helpers/ui_test_ids.rb +49 -0
  111. data/lib/smriti/jobs/adapter.rb +81 -0
  112. data/lib/smriti/service_response.rb +75 -0
  113. data/lib/smriti/services/base_service.rb +471 -0
  114. data/lib/smriti/services/check_matview_exists.rb +76 -0
  115. data/lib/smriti/services/concurrent_refresh.rb +94 -0
  116. data/lib/smriti/services/create_view.rb +173 -0
  117. data/lib/smriti/services/delete_view.rb +111 -0
  118. data/lib/smriti/services/regular_refresh.rb +90 -0
  119. data/lib/smriti/services/swap_refresh.rb +181 -0
  120. data/lib/smriti/version.rb +21 -0
  121. data/lib/smriti.rb +64 -0
  122. data/lib/tasks/helpers.rb +185 -0
  123. data/lib/tasks/smriti_tasks.rake +151 -0
  124. metadata +206 -0
@@ -0,0 +1,470 @@
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: DrawerController
10
+ * -------------------------------------
11
+ * Controls a slide-in drawer backed by a Turbo Frame. Supports opening via
12
+ * triggers, deep-linking with a query param, refreshing content, and handling
13
+ * Turbo submission outcomes.
14
+ *
15
+ * Responsibilities:
16
+ * - Open/close drawer; manage trigger stack for nested opens.
17
+ * - Load frame content from trigger attributes or by name (router-style).
18
+ * - Sync drawer state with a query parameter for deep links.
19
+ * - Update header/title from loaded frame content.
20
+ * - Handle Turbo events:
21
+ * - 299 → clear param + full page reload
22
+ * - 298 → close drawer
23
+ * - others → refresh active datatable
24
+ *
25
+ * Key Components:
26
+ * - Public actions: `open`, `show`, `close`, `refresh`, `refreshActiveDatatable`, `refreshFrameById`, `openDrawerByName`, `load`
27
+ * - Frame events: `_handleFrameLoad`, `_handleSubmitEnd`
28
+ * - Helpers: `_installFrameListeners`, `_removeFrameListeners`, `_loadFromTrigger`,
29
+ * `_updateHeader`, `_hideDrawer`, `_rootElement`, `_handleEscape`,
30
+ * `_clearQueryParam`, `_setQueryParam`, `_openFromQueryParam`,
31
+ * `_openRun`, `_openPreferences`, `_openDefinition`
32
+ */
33
+
34
+ import { Controller } from "@hotwired/stimulus";
35
+
36
+ /**
37
+ * @class DrawerController
38
+ * @extends Controller
39
+ */
40
+ export default class extends Controller {
41
+ /**
42
+ * Targets:
43
+ * - root: container element for the drawer (optional; falls back to this.element)
44
+ * - overlay: backdrop element (optional)
45
+ * - panel: the drawer panel (optional)
46
+ * - frame: Turbo Frame inside the drawer
47
+ * - header: header element (for aria-label updates)
48
+ * - title: element for visible title text
49
+ */
50
+ static targets = ["root", "overlay", "panel", "frame", "header", "title"];
51
+
52
+ /**
53
+ * CSS classes:
54
+ * - open: toggled on the root to show/hide the drawer
55
+ */
56
+ static classes = ["open"];
57
+
58
+ /**
59
+ * Values:
60
+ * - queryParam: name of the URL query parameter to reflect drawer state
61
+ */
62
+ static values = {
63
+ queryParam: { type: String, default: "open" },
64
+ };
65
+
66
+ /**
67
+ * Initialize internal state and bind handlers.
68
+ * @return {void}
69
+ */
70
+ initialize() {
71
+ this.currentTargetStack = [];
72
+ this.triggerEl = null;
73
+ this._handleEscape = this._handleEscape.bind(this);
74
+ this._handleSubmitEnd = this._handleSubmitEnd.bind(this);
75
+ this._handleFrameLoad = this._handleFrameLoad.bind(this);
76
+ }
77
+
78
+ /**
79
+ * Lifecycle: connect
80
+ * Installs frame listeners and opens drawer if query param is present.
81
+ * @return {void}
82
+ */
83
+ connect() {
84
+ this._installFrameListeners();
85
+ this._openFromQueryParam();
86
+ }
87
+
88
+ /**
89
+ * Lifecycle: disconnect
90
+ * Removes listeners.
91
+ * @return {void}
92
+ */
93
+ disconnect() {
94
+ this._removeFrameListeners();
95
+ document.removeEventListener("keydown", this._handleEscape);
96
+ }
97
+
98
+ // ── Public actions ───────────────────────────────────────────────
99
+
100
+ /**
101
+ * Opens the drawer for the current trigger (pushes onto trigger stack).
102
+ * @param {Event} [event]
103
+ * @return {void}
104
+ */
105
+ open(event) {
106
+ if (event) {
107
+ event.preventDefault();
108
+ event.stopPropagation();
109
+ if (event.stopImmediatePropagation) event.stopImmediatePropagation();
110
+ if (event.currentTarget) {
111
+ this.currentTargetStack.push(event.currentTarget);
112
+ this.triggerEl = event.currentTarget;
113
+ }
114
+ }
115
+ this._loadFromTrigger();
116
+ }
117
+
118
+ /**
119
+ * Shows the drawer (adds open class, sets aria, enables ESC handler).
120
+ * @return {void}
121
+ */
122
+ show() {
123
+ document.addEventListener("keydown", this._handleEscape);
124
+ const root = this._rootElement();
125
+ root.classList.add(this.openClass);
126
+ root.setAttribute("aria-hidden", "false");
127
+ this.overlayTarget?.setAttribute("aria-hidden", "false");
128
+ }
129
+
130
+ /**
131
+ * Closes the drawer. If there is a previous trigger on the stack,
132
+ * it loads that trigger instead (nested/stacked draws).
133
+ * @return {void}
134
+ */
135
+ close() {
136
+ this.currentTargetStack.pop();
137
+ this.triggerEl = this.currentTargetStack.at(-1) || null;
138
+
139
+ if (this.triggerEl) {
140
+ this._loadFromTrigger();
141
+ return;
142
+ }
143
+
144
+ this._hideDrawer();
145
+ this._clearQueryParam();
146
+ if (this.hasFrameTarget) this.frameTarget.removeAttribute("src");
147
+ }
148
+
149
+ /**
150
+ * Reloads the drawer frame if it has a `src`.
151
+ * @return {void}
152
+ */
153
+ refresh() {
154
+ if (this.frameTarget?.src) {
155
+ this.load(this.frameTarget.src);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Dispatches a datatable refresh event for the active page.
161
+ * @return {void}
162
+ */
163
+ refreshActiveDatatable() {
164
+ // First notify the Turbo Frame Lifecycle controller to flush busy states
165
+ // in case any datatables were affected by the drawer action.
166
+ // Subsiquently, turbo_frame_lifecycle_controller.js will notify datatables.
167
+ document.dispatchEvent(
168
+ new CustomEvent("drawer:refresh", {
169
+ bubbles: false,
170
+ cancelable: false,
171
+ }),
172
+ );
173
+ }
174
+
175
+ /**
176
+ * Hard-refreshes a Turbo Frame by id, cache-busting with a timestamp param.
177
+ * @param {string} id
178
+ * @return {void}
179
+ */
180
+ refreshFrameById(id) {
181
+ const frame = document.getElementById(id);
182
+ if (!frame) return;
183
+ const src = frame.getAttribute("src") || frame.dataset.src;
184
+ if (!src) return;
185
+
186
+ try {
187
+ const url = new URL(src, window.location.href);
188
+ url.searchParams.set("_", Date.now().toString());
189
+ frame.src = url.toString();
190
+ } catch {
191
+ const separator = src.includes("?") ? "&" : "?";
192
+ frame.src = `${src}${separator}_=${Date.now()}`;
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Opens a drawer route by a logical name (e.g., "definitions_new_",
198
+ * "definitions_view_42", "preferences_edit_", "runs_view_123").
199
+ * @param {string} name - Pattern: "<type>_<action>_<id?>"
200
+ * @return {void}
201
+ */
202
+ openDrawerByName(name) {
203
+ if (!name) return;
204
+ const [type, action, id] = name.split("_");
205
+ switch (type) {
206
+ case "definitions":
207
+ this._openDefinition(action, id);
208
+ break;
209
+ case "runs":
210
+ this._openRun(action, id);
211
+ break;
212
+ case "preferences":
213
+ this._openPreferences(action);
214
+ break;
215
+ default:
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Loads a URL into the drawer’s Turbo Frame.
221
+ * @param {string} url
222
+ * @return {void}
223
+ */
224
+ load(url) {
225
+ if (!this.hasFrameTarget || !url) return;
226
+ try {
227
+ const resolved = new URL(url, window.location.href);
228
+ this.frameTarget.src = resolved.toString();
229
+ } catch {
230
+ this.frameTarget.src = url;
231
+ }
232
+ }
233
+
234
+ // ── Frame events ─────────────────────────────────────────────────
235
+
236
+ /**
237
+ * Handles Turbo frame load: updates title and sets deep-link param if present.
238
+ * Expects hidden inputs inside the frame:
239
+ * - #mv-drawer-title-text
240
+ * - #mv-drawer-open-url-identifier
241
+ * @param {Event} event
242
+ * @return {void}
243
+ */
244
+ _handleFrameLoad(event) {
245
+ const frame = event.target;
246
+ const title = frame?.querySelector("#mv-drawer-title-text")?.value;
247
+ if (title) this._updateHeader(title);
248
+
249
+ const identifier = frame?.querySelector(
250
+ "#mv-drawer-open-url-identifier",
251
+ )?.value;
252
+ if (identifier) this._setQueryParam(identifier);
253
+ }
254
+
255
+ /**
256
+ * Handles Turbo submit-end results for drawer forms.
257
+ * Status codes:
258
+ * - 299: Clear param and full reload.
259
+ * - 298: Close drawer.
260
+ * - otherwise: refresh active datatable.
261
+ * @param {CustomEvent} event
262
+ * @return {void}
263
+ */
264
+ _handleSubmitEnd(event) {
265
+ const statusCode = event.detail.fetchResponse.statusCode;
266
+ if (statusCode === 299) {
267
+ this._clearQueryParam();
268
+ window.location.reload();
269
+ return;
270
+ }
271
+ if (statusCode === 298) {
272
+ this.close();
273
+ this.refreshActiveDatatable();
274
+ }
275
+ }
276
+
277
+ // ── Internal helpers ────────────────────────────────────────────
278
+
279
+ /**
280
+ * Attaches Turbo event listeners to the drawer frame.
281
+ * @return {void}
282
+ */
283
+ _installFrameListeners() {
284
+ if (!this.hasFrameTarget) return;
285
+ this.frameTarget.addEventListener(
286
+ "turbo:submit-end",
287
+ this._handleSubmitEnd,
288
+ );
289
+ this.frameTarget.addEventListener(
290
+ "turbo:frame-load",
291
+ this._handleFrameLoad,
292
+ );
293
+ }
294
+
295
+ /**
296
+ * Removes Turbo event listeners from the drawer frame.
297
+ * @return {void}
298
+ */
299
+ _removeFrameListeners() {
300
+ if (!this.hasFrameTarget) return;
301
+ this.frameTarget.removeEventListener(
302
+ "turbo:submit-end",
303
+ this._handleSubmitEnd,
304
+ );
305
+ this.frameTarget.removeEventListener(
306
+ "turbo:frame-load",
307
+ this._handleFrameLoad,
308
+ );
309
+ }
310
+
311
+ /**
312
+ * Loads URL/title from the top-of-stack trigger and opens the drawer.
313
+ * Trigger attributes:
314
+ * - data-drawer-title / title / aria-label → title
315
+ * - data-drawer-url / href → URL
316
+ * @return {void}
317
+ */
318
+ _loadFromTrigger() {
319
+ const trigger = this.currentTargetStack.at(-1);
320
+ if (!trigger) return;
321
+
322
+ const title =
323
+ trigger.dataset.drawerTitle ||
324
+ trigger.getAttribute("title") ||
325
+ trigger.getAttribute("aria-label") ||
326
+ "";
327
+ const url =
328
+ trigger.dataset.drawerUrl || trigger.getAttribute("href") || null;
329
+
330
+ if (!url || url === "#" || url.startsWith("javascript")) return;
331
+
332
+ this._updateHeader(title);
333
+ this.show();
334
+ this.load(url);
335
+ }
336
+
337
+ /**
338
+ * Updates header aria-label and visible title (if present).
339
+ * @param {string} title
340
+ * @return {void}
341
+ */
342
+ _updateHeader(title) {
343
+ if (!this.hasHeaderTarget) return;
344
+ this.headerTarget.setAttribute("aria-label", title || "");
345
+ if (this.hasTitleTarget) {
346
+ this.titleTarget.textContent = title || "";
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Hides the drawer and overlay, removes ESC handler, and blurs focus.
352
+ * @return {void}
353
+ */
354
+ _hideDrawer() {
355
+ document.removeEventListener("keydown", this._handleEscape);
356
+ const root = this._rootElement();
357
+ root.classList.remove(this.openClass);
358
+ root.setAttribute("aria-hidden", "true");
359
+ if (this.hasOverlayTarget) {
360
+ this.overlayTarget.setAttribute("aria-hidden", "true");
361
+ }
362
+ document.activeElement?.blur();
363
+ document.body.focus();
364
+ }
365
+
366
+ /**
367
+ * Resolves the root element: `rootTarget` if present, otherwise controller element.
368
+ * @return {HTMLElement}
369
+ */
370
+ _rootElement() {
371
+ return this.hasRootTarget ? this.rootTarget : this.element;
372
+ }
373
+
374
+ /**
375
+ * Closes the drawer on Escape key.
376
+ * @param {KeyboardEvent} event
377
+ * @return {void}
378
+ */
379
+ _handleEscape(event) {
380
+ if (event.key === "Escape") this.close();
381
+ }
382
+
383
+ /**
384
+ * Clears the deep-link query param.
385
+ * @return {void}
386
+ */
387
+ _clearQueryParam() {
388
+ const url = new URL(window.location.href);
389
+ url.searchParams.delete(this.queryParamValue);
390
+ history.replaceState({}, "", url);
391
+ }
392
+
393
+ /**
394
+ * Sets the deep-link query param to `value`.
395
+ * @param {string} value
396
+ * @return {void}
397
+ */
398
+ _setQueryParam(value) {
399
+ const url = new URL(window.location.href);
400
+ url.searchParams.set(this.queryParamValue, value);
401
+ history.replaceState({}, "", url);
402
+ }
403
+
404
+ /**
405
+ * Opens the drawer from the current page’s query param (if present).
406
+ * @return {void}
407
+ */
408
+ _openFromQueryParam() {
409
+ const param = new URLSearchParams(window.location.search).get(
410
+ this.queryParamValue,
411
+ );
412
+ if (param) this.openDrawerByName(param);
413
+ }
414
+
415
+ /**
416
+ * Opens a run in view mode.
417
+ * @param {string} action - expected "view"
418
+ * @param {string|number} id
419
+ * @return {void}
420
+ */
421
+ _openRun(action, id) {
422
+ if (action !== "view") return;
423
+ this._updateHeader("");
424
+ this.show();
425
+ this.load(`${window.SmritiRoutes.runsPath}/${id}?frame_id=mv-drawer`);
426
+ }
427
+
428
+ /**
429
+ * Opens preferences in edit mode.
430
+ * @param {string} action - expected "edit"
431
+ * @return {void}
432
+ */
433
+ _openPreferences(action) {
434
+ if (action !== "edit") return;
435
+ this._updateHeader("");
436
+ this.show();
437
+ this.load(`${window.SmritiRoutes.preferencesPath}?frame_id=mv-drawer`);
438
+ }
439
+
440
+ /**
441
+ * Opens a definition in new/view/edit mode based on `action`.
442
+ * @param {"new"|"view"|"edit"} action
443
+ * @param {string|number} id
444
+ * @return {void}
445
+ */
446
+ _openDefinition(action, id) {
447
+ this._updateHeader("");
448
+ switch (action) {
449
+ case "new":
450
+ this.show();
451
+ this.load(
452
+ `${window.SmritiRoutes.definitionsPath}/new?frame_id=mv-drawer`,
453
+ );
454
+ break;
455
+ case "view":
456
+ this.show();
457
+ this.load(
458
+ `${window.SmritiRoutes.definitionsPath}/${id}?frame_id=mv-drawer`,
459
+ );
460
+ break;
461
+ case "edit":
462
+ this.show();
463
+ this.load(
464
+ `${window.SmritiRoutes.definitionsPath}/${id}/edit?frame_id=mv-drawer`,
465
+ );
466
+ break;
467
+ default:
468
+ }
469
+ }
470
+ }
@@ -0,0 +1,112 @@
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: FlashController
10
+ * ------------------------------------
11
+ * Handles automatic and manual dismissal of flash messages or alerts.
12
+ *
13
+ * Responsibilities:
14
+ * - Automatically removes flash after a configurable duration.
15
+ * - Supports manual dismissal through a “click-to-dismiss” action.
16
+ * - Applies an optional fade-out class before removal for smooth transition.
17
+ *
18
+ * Key Components:
19
+ * - Public: `dismiss`
20
+ * - Internal: `_scheduleRemoval`, `_clearTimer`, `_finalizeRemoval`
21
+ */
22
+
23
+ import { Controller } from "@hotwired/stimulus";
24
+
25
+ /**
26
+ * @class FlashController
27
+ * @extends Controller
28
+ */
29
+ export default class extends Controller {
30
+ /**
31
+ * Values:
32
+ * - duration: how long (ms) before the flash auto-dismisses (default: 10 000).
33
+ */
34
+ static values = {
35
+ duration: { type: Number, default: 10000 },
36
+ };
37
+
38
+ /**
39
+ * CSS classes:
40
+ * - dismiss: class applied before removal (e.g., fade-out transition).
41
+ */
42
+ static classes = ["dismiss"];
43
+
44
+ /**
45
+ * Lifecycle: connect
46
+ * Starts the auto-removal timer.
47
+ * @return {void}
48
+ */
49
+ connect() {
50
+ this._scheduleRemoval();
51
+ }
52
+
53
+ /**
54
+ * Lifecycle: disconnect
55
+ * Clears pending timers when controller disconnects.
56
+ * @return {void}
57
+ */
58
+ disconnect() {
59
+ this._clearTimer();
60
+ }
61
+
62
+ /**
63
+ * Manually dismisses the flash message (e.g., via close button).
64
+ * Cancels any scheduled auto-removal and removes the element immediately.
65
+ * @param {Event} [event]
66
+ * @return {void}
67
+ */
68
+ dismiss(event) {
69
+ if (event) event.preventDefault();
70
+ this._clearTimer();
71
+ this._finalizeRemoval();
72
+ }
73
+
74
+ // ── Internal helpers ────────────────────────────────────────────
75
+
76
+ /**
77
+ * Starts a timeout to remove the element after the configured duration.
78
+ * Does nothing if duration ≤ 0.
79
+ * @return {void}
80
+ */
81
+ _scheduleRemoval() {
82
+ if (this.durationValue <= 0) return;
83
+ this._timer = window.setTimeout(
84
+ () => this._finalizeRemoval(),
85
+ this.durationValue,
86
+ );
87
+ }
88
+
89
+ /**
90
+ * Cancels the scheduled removal timer.
91
+ * @return {void}
92
+ */
93
+ _clearTimer() {
94
+ if (this._timer) {
95
+ clearTimeout(this._timer);
96
+ this._timer = null;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Removes the flash element, optionally adding a dismiss animation class.
102
+ * @return {void}
103
+ */
104
+ _finalizeRemoval() {
105
+ if (this.hasDismissClass) {
106
+ this.element.classList.add(this.dismissClass);
107
+ window.setTimeout(() => this.element.remove(), 150);
108
+ } else {
109
+ this.element.remove();
110
+ }
111
+ }
112
+ }
@@ -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 "smriti/controllers/application";
8
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
9
+
10
+ eagerLoadControllersFrom("smriti/controllers", application);