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,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);
|