active_element 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -0
  3. data/.strong_versions.yml +2 -0
  4. data/Gemfile +10 -2
  5. data/Gemfile.lock +229 -4
  6. data/Rakefile +1 -0
  7. data/active_element.gemspec +7 -0
  8. data/app/assets/config/active_element/manifest.js +2 -0
  9. data/app/assets/javascripts/active_element/application.js +10 -0
  10. data/app/assets/javascripts/active_element/confirm.js +67 -0
  11. data/app/assets/javascripts/active_element/form.js +61 -0
  12. data/app/assets/javascripts/active_element/json_field.js +316 -0
  13. data/app/assets/javascripts/active_element/pagination.js +18 -0
  14. data/app/assets/javascripts/active_element/search_field.js +127 -0
  15. data/app/assets/javascripts/active_element/secret.js +40 -0
  16. data/app/assets/javascripts/active_element/setup.js +36 -0
  17. data/app/assets/javascripts/active_element/theme.js +42 -0
  18. data/app/assets/stylesheets/active_element/_variables.scss +142 -0
  19. data/app/assets/stylesheets/active_element/application.scss +77 -0
  20. data/app/controllers/active_element/application_controller.rb +41 -0
  21. data/app/controllers/active_element/text_searches_controller.rb +189 -0
  22. data/app/views/active_element/components/_horizontal_tabs.html.erb +32 -0
  23. data/app/views/active_element/components/_vertical_tabs.html.erb +38 -0
  24. data/app/views/active_element/components/button.html.erb +27 -0
  25. data/app/views/active_element/components/fields/_boolean.html.erb +11 -0
  26. data/app/views/active_element/components/form/_check_box.html.erb +3 -0
  27. data/app/views/active_element/components/form/_check_boxes.html.erb +33 -0
  28. data/app/views/active_element/components/form/_field.html.erb +28 -0
  29. data/app/views/active_element/components/form/_generic_field.html.erb +3 -0
  30. data/app/views/active_element/components/form/_json.html.erb +12 -0
  31. data/app/views/active_element/components/form/_label.html.erb +17 -0
  32. data/app/views/active_element/components/form/_option_groups_summary.html.erb +17 -0
  33. data/app/views/active_element/components/form/_select.html.erb +4 -0
  34. data/app/views/active_element/components/form/_summary.html.erb +40 -0
  35. data/app/views/active_element/components/form/_templates.html.erb +85 -0
  36. data/app/views/active_element/components/form/_text_area.html.erb +4 -0
  37. data/app/views/active_element/components/form/_text_search.html.erb +16 -0
  38. data/app/views/active_element/components/form.html.erb +78 -0
  39. data/app/views/active_element/components/json.html.erb +8 -0
  40. data/app/views/active_element/components/page_description.html.erb +3 -0
  41. data/app/views/active_element/components/secret/_field.html.erb +1 -0
  42. data/app/views/active_element/components/secret/_templates.html.erb +11 -0
  43. data/app/views/active_element/components/table/_collection_row.html.erb +30 -0
  44. data/app/views/active_element/components/table/_grouped_collection.html.erb +88 -0
  45. data/app/views/active_element/components/table/_pagination.html.erb +17 -0
  46. data/app/views/active_element/components/table/_ungrouped_collection.html.erb +49 -0
  47. data/app/views/active_element/components/table/collection.html.erb +39 -0
  48. data/app/views/active_element/components/table/item.html.erb +39 -0
  49. data/app/views/active_element/components/tabs.html.erb +7 -0
  50. data/app/views/active_element/decorators/_boolean.html.erb +5 -0
  51. data/app/views/active_element/decorators/_date.html.erb +3 -0
  52. data/app/views/active_element/decorators/_datetime.html.erb +3 -0
  53. data/app/views/active_element/decorators/_time.html.erb +3 -0
  54. data/app/views/active_element/forbidden.html.erb +33 -0
  55. data/app/views/active_element/main_menu/_item.html.erb +9 -0
  56. data/app/views/active_element/navbar/_menu.html.erb +30 -0
  57. data/app/views/active_element/theme/_select.html.erb +1 -0
  58. data/app/views/active_element/theme/_templates.html.erb +6 -0
  59. data/app/views/kaminari/_first_page.html.erb +3 -0
  60. data/app/views/kaminari/_gap.html.erb +3 -0
  61. data/app/views/kaminari/_last_page.html.erb +3 -0
  62. data/app/views/kaminari/_next_page.html.erb +3 -0
  63. data/app/views/kaminari/_page.html.erb +9 -0
  64. data/app/views/kaminari/_paginator.html.erb +17 -0
  65. data/app/views/kaminari/_prev_page.html.erb +3 -0
  66. data/app/views/layouts/active_element.html.erb +65 -0
  67. data/app/views/layouts/active_element_error.html.erb +40 -0
  68. data/config/routes.rb +5 -0
  69. data/lib/active_element/active_menu_link.rb +80 -0
  70. data/lib/active_element/active_record_text_search_authorization.rb +12 -0
  71. data/lib/active_element/colorized_string.rb +33 -0
  72. data/lib/active_element/component.rb +122 -0
  73. data/lib/active_element/components/button.rb +156 -0
  74. data/lib/active_element/components/collection_table.rb +118 -0
  75. data/lib/active_element/components/form.rb +210 -0
  76. data/lib/active_element/components/item_table.rb +57 -0
  77. data/lib/active_element/components/json.rb +31 -0
  78. data/lib/active_element/components/link_helpers.rb +9 -0
  79. data/lib/active_element/components/page_description.rb +28 -0
  80. data/lib/active_element/components/secret_fields.rb +15 -0
  81. data/lib/active_element/components/tab.rb +37 -0
  82. data/lib/active_element/components/tabs.rb +35 -0
  83. data/lib/active_element/components/translations.rb +18 -0
  84. data/lib/active_element/components/util/association_mapping.rb +80 -0
  85. data/lib/active_element/components/util/decorator.rb +107 -0
  86. data/lib/active_element/components/util/display_value_mapping.rb +48 -0
  87. data/lib/active_element/components/util/field_mapping.rb +144 -0
  88. data/lib/active_element/components/util/form_field_mapping.rb +104 -0
  89. data/lib/active_element/components/util/form_value_mapping.rb +49 -0
  90. data/lib/active_element/components/util/i18n.rb +66 -0
  91. data/lib/active_element/components/util/record_mapping.rb +111 -0
  92. data/lib/active_element/components/util.rb +43 -0
  93. data/lib/active_element/components.rb +20 -0
  94. data/lib/active_element/controller_action.rb +91 -0
  95. data/lib/active_element/engine.rb +26 -0
  96. data/lib/active_element/permissions_check.rb +101 -0
  97. data/lib/active_element/rails_component.rb +40 -0
  98. data/lib/active_element/route.rb +112 -0
  99. data/lib/active_element/routes.rb +62 -0
  100. data/lib/active_element/version.rb +1 -1
  101. data/lib/active_element.rb +91 -1
  102. data/lib/tasks/active_element.rake +23 -0
  103. data/rspec-documentation/dummy +1 -0
  104. data/rspec-documentation/pages/Components/Forms.md +1 -0
  105. data/rspec-documentation/pages/Components/Tables.md +47 -0
  106. data/rspec-documentation/pages/Components/Tabs.md +1 -0
  107. data/rspec-documentation/pages/Components.md +1 -0
  108. data/rspec-documentation/pages/Decorators/Inline Decorators.md +1 -0
  109. data/rspec-documentation/pages/Decorators/View Decorators.md +1 -0
  110. data/rspec-documentation/pages/Index.md +3 -0
  111. data/rspec-documentation/pages/Util/I18n.md +1 -0
  112. data/rspec-documentation/spec_helper.rb +35 -0
  113. metadata +191 -3
@@ -0,0 +1,316 @@
1
+ ActiveElement.JsonField = (() => {
2
+ const cloneElement = (id) => ActiveElement.cloneElement('json', id);
3
+
4
+ const humanize = ({ string, singular = false }) => {
5
+ const humanized = string.split('_').map(item => item.charAt(0).toUpperCase() + item.substring(1)).join(' ');
6
+
7
+ if (!singular) return humanized;
8
+
9
+ return humanized.replace(/s$/, ''); // FIXME: Expose translations from back-end to make this more useful.
10
+ };
11
+
12
+ const createStore = ({ data, store = { data: {}, paths: {} } }) => {
13
+ const buildState = ({ data, store, path = [] }) => {
14
+ const getPath = (key) => {
15
+ return path.concat([key]);
16
+ };
17
+
18
+ if (Array.isArray(data)) {
19
+ return data.map((value, index) => buildState({ data: value, store, path: getPath(index) }));
20
+ } else if (data && typeof(data) === 'object') {
21
+ return Object.fromEntries(
22
+ Object.entries(data).map(
23
+ ([key, value]) => [key, buildState({ data: value, store, path: getPath(key) })]
24
+ )
25
+ );
26
+ } else {
27
+ const id = crypto.randomUUID();
28
+ store.data[id] = data;
29
+ store.paths[id] = path;
30
+ return id;
31
+ }
32
+ };
33
+
34
+ const state = buildState({ data, store });
35
+ const getValue = (key) => store.data[key];
36
+ const setValue = (key, value) => store.data[key] = value;
37
+
38
+ return { state, store, getValue, setValue };
39
+ };
40
+
41
+ const getState = ({ store }) => {
42
+ const data = {};
43
+ const storeData = Object.entries(store.paths).forEach(([id, path]) => {
44
+ let value = data;
45
+ path.forEach((key, index) => {
46
+ if (index === path.length - 1) {
47
+ value[key] = store.data[id];
48
+ } else if (typeof(key) === 'string') {
49
+ value[key] = value[key] || {};
50
+ value = value[key];
51
+ } else {
52
+ value[key] = value[key] || [];
53
+ value = value[key];
54
+ }
55
+ });
56
+
57
+ return value;
58
+ },
59
+ {});
60
+
61
+ return data;
62
+ };
63
+
64
+ const getValueFromElement = ({ element }) => {
65
+ if (element.type === 'checkbox') return element.checked;
66
+
67
+ return element.value;
68
+ };
69
+
70
+ const getData = (element) => {
71
+ const dataKey = element.dataset.dataKey;
72
+
73
+ return ActiveElement.jsonData[dataKey].data;
74
+ };
75
+
76
+ const getSchema = (element) => {
77
+ const dataKey = element.dataset.dataKey;
78
+
79
+ return ActiveElement.jsonData[dataKey].schema;
80
+ };
81
+
82
+ const trackState = ({ element, schema, getValue }) => {
83
+ element.addEventListener('change', (ev) => {
84
+ const key = ev.target.id;
85
+ const previousValue = getValue(key);
86
+ const newValue = getValueFromElement({ element: ev.target });
87
+
88
+ if (previousValue !== newValue) {
89
+ // setValue(key, newValue);
90
+ // TODO: Trigger callbacks
91
+ }
92
+ console.log(`Previous: ${previousValue}`);
93
+ console.log(`Updated: ${newValue}`);
94
+ return true;
95
+ });
96
+ };
97
+
98
+ const Component = ({ getValue, schema, state, element }) => {
99
+ const ObjectField = ({ schema, state, floating = true, omitLabel = false }) => {
100
+ let element;
101
+ switch (schema.type) {
102
+ case 'boolean':
103
+ return BooleanField({ state, omitLabel, schema });
104
+ case 'string':
105
+ return StringField({ state, omitLabel, floating, schema });
106
+ break;
107
+ case 'object':
108
+ element = cloneElement('form-group-floating');
109
+
110
+ (schema.shape.fields).forEach((field) => {
111
+ element.append(
112
+ ObjectField({
113
+ name: field.name,
114
+ floating: false,
115
+ schema: field,
116
+ state: state ? state[field.name] : null,
117
+ })
118
+ );
119
+ });
120
+
121
+ return element;
122
+ case 'array':
123
+ element = cloneElement('form-group');
124
+ const list = ArrayField({ schema, state });
125
+ element.append(ExpandCollapseButton({ element }));
126
+ element.append(Label({ title: schema.name }));
127
+ element.append(list);
128
+ element.append(AppendButton({ list, schema, state }));
129
+ return element;
130
+ }
131
+ };
132
+
133
+ const BooleanField = ({ omitLabel, schema, state }) => {
134
+ const checkbox = cloneElement('checkbox-field');
135
+
136
+ checkbox.id = state;
137
+ checkbox.checked = getValue(state);
138
+
139
+ if (omitLabel) return checkbox;
140
+
141
+ const element = cloneElement('form-check');
142
+
143
+ element.append(checkbox);
144
+ element.append(Label({ title: schema.name, template: 'form-check-label' }));
145
+
146
+ return element;
147
+ };
148
+
149
+ const ArrayField = ({ schema, state }) => {
150
+ const element = cloneElement('list-group');
151
+
152
+ if (state) {
153
+ state.forEach((value) => {
154
+ const listItem = cloneElement('list-item');
155
+ const objectField = ObjectField({
156
+ omitLabel: true,
157
+ schema: { ...schema, ...schema.shape },
158
+ state: value,
159
+ });
160
+
161
+ if (schema.shape.type == 'object') {
162
+ const group = cloneElement('form-group');
163
+ group.append(DeleteButton({ rootElement: listItem, template: 'delete-object-button' }));
164
+ group.append(objectField);
165
+ listItem.append(group);
166
+ } else {
167
+ listItem.append(objectField);
168
+ listItem.append(DeleteButton({ rootElement: listItem }));
169
+ }
170
+
171
+ element.append(listItem);
172
+ });
173
+ }
174
+
175
+ return element;
176
+ };
177
+
178
+ const Label = ({ title, template }) => {
179
+ const element = cloneElement(template || 'label');
180
+
181
+ element.append(humanize({ string: title }));
182
+
183
+ return element;
184
+ }
185
+
186
+ const Select = ({ state, schema }) => {
187
+ const element = cloneElement('select')
188
+
189
+ element.id = state;
190
+
191
+ schema.shape.options.forEach((option) => {
192
+ const optionElement = document.createElement('option');
193
+ optionElement.value = option;
194
+ optionElement.append(option);
195
+ optionElement.selected = option === getValue(state);
196
+ element.append(optionElement);
197
+ });
198
+
199
+ return element;
200
+ };
201
+
202
+ const TextField = ({ template, state, schema }) => {
203
+ const element = cloneElement(template || 'text-field');
204
+
205
+ element.value = getValue(state) || '';
206
+ element.id = state;
207
+ element.placeholder = schema.shape?.placeholder || ' ';
208
+
209
+ return element;
210
+ };
211
+
212
+ const StringField = ({ omitLabel, floating, schema, state }) => {
213
+ let element;
214
+
215
+ if (schema.shape?.options?.length) {
216
+ element = Select({ state, schema });
217
+ } else {
218
+ element = TextField({ state, schema });
219
+ }
220
+
221
+ if (omitLabel) return element;
222
+
223
+ const group = cloneElement('form-group-floating');
224
+
225
+ group.append(element);
226
+ group.append(Label({ title: schema.name }));
227
+
228
+ return group;
229
+ };
230
+
231
+ const ExpandCollapseButton = ({ element }) => {
232
+ const button = cloneElement('expand-collapse-button');
233
+
234
+ button.onclick = (ev) => {
235
+ ev.stopPropagation();
236
+ element.classList.toggle('collapsed');
237
+
238
+ if (element.classList.contains('collapsed')) {
239
+ button.innerText = 'Show';
240
+ } else {
241
+ button.innerText = 'Hide';
242
+ }
243
+
244
+ return false;
245
+ };
246
+
247
+ return button;
248
+ };
249
+
250
+ const DeleteButton = ({ rootElement, template = 'delete-button' }) => {
251
+ const element = cloneElement(template);
252
+
253
+ element.onclick = (ev) => {
254
+ ev.stopPropagation();
255
+ rootElement.remove();
256
+
257
+ return false;
258
+ };
259
+
260
+ return element;
261
+ };
262
+
263
+ const AppendButton = ({ list, schema, state }) => {
264
+ const element = cloneElement('append-button');
265
+
266
+ const humanName = humanize({ string: schema.name, singular: true });
267
+
268
+ element.append(`Add ${humanName}`);
269
+ element.onclick = (ev) => {
270
+ ev.stopPropagation();
271
+ const listItem = cloneElement('list-item');
272
+ const objectField = ObjectField(
273
+ { name: schema.name, omitLabel: true, state, schema: { ...schema, ...schema.shape } }
274
+ );
275
+
276
+ if (schema.shape.type == 'object') {
277
+ listItem.append(DeleteButton({ rootElement: listItem, template: 'delete-object-button' }));
278
+ listItem.append(objectField);
279
+ } else {
280
+ listItem.append(objectField);
281
+ listItem.append(DeleteButton({ rootElement: listItem }));
282
+ }
283
+ list.append(listItem);
284
+
285
+ return false;
286
+ };
287
+
288
+ return element;
289
+ };
290
+
291
+ element.append(ObjectField({ omitLabel: true, schema, state, getValue }));
292
+ };
293
+
294
+ const JsonField = (element) => {
295
+ const data = getData(element);
296
+ const schema = getSchema(element);
297
+ const { state, store, getValue } = createStore({ data });
298
+
299
+ console.log(getState({ store }));
300
+
301
+ trackState({ element, schema, getValue });
302
+ const component = Component({ getValue, schema, state, element });
303
+
304
+ return component;
305
+ };
306
+
307
+ return JsonField;
308
+ })();
309
+
310
+ (() => {
311
+ window.addEventListener('DOMContentLoaded', () => {
312
+ document.querySelectorAll('.json-field').forEach((element) => {
313
+ ActiveElement.JsonField(element);
314
+ });
315
+ });
316
+ })();
@@ -0,0 +1,18 @@
1
+ (() => {
2
+ window.addEventListener('DOMContentLoaded', () => {
3
+ const paginationSelect = document.querySelector('#collection-table-page-size-selector');
4
+
5
+ if (paginationSelect) {
6
+ paginationSelect.addEventListener('change', (ev) => {
7
+ ev.stopPropagation();
8
+
9
+ const params = new URLSearchParams(window.location.search);
10
+
11
+ params.set('page_size', ev.target.value);
12
+ window.location.search = params.toString();
13
+
14
+ return false;
15
+ });
16
+ }
17
+ });
18
+ })();
@@ -0,0 +1,127 @@
1
+ (() => {
2
+ let lastRequestId;
3
+
4
+ const cloneElement = (id) => ActiveElement.cloneElement('form-search-field', id);
5
+
6
+ const tryParseJSON = (json, defaultValue) => {
7
+ try {
8
+ return JSON.parse(json);
9
+ } catch (error) {
10
+ ActiveElement.log(error);
11
+ return defaultValue;
12
+ }
13
+ };
14
+
15
+ const processResponse = ({
16
+ element, response, hiddenInput, spinner, clearButton, searchResultsContainer, responseErrorContainer
17
+ }) => {
18
+ spinner.classList.add('invisible');
19
+ clearButton.classList.remove('invisible');
20
+
21
+ if (response.ok) {
22
+ response.json().then((json) => {
23
+ if (json.request_id !== lastRequestId) {
24
+ return;
25
+ }
26
+
27
+ responseErrorContainer.innerText = '';
28
+
29
+ if (!json.results.length) {
30
+ responseErrorContainer.innerText = `No matching results for ${element.value}`;
31
+ return;
32
+ }
33
+
34
+ json.results.forEach(({ value, attributes }) => {
35
+ const resultsItem = cloneElement('results-item');
36
+
37
+ resultsItem.innerText = attributes.length === 0 ? value : `${attributes.join(', ')} (${value})`;
38
+ resultsItem.addEventListener('click', () => {
39
+ hiddenInput.value = value;
40
+ element.value = attributes.length == 0 ? value : `${attributes.join(', ')} (${value})`;
41
+ searchResultsContainer.replaceChildren();
42
+ searchResultsContainer.classList.add('d-none');
43
+ });
44
+ searchResultsContainer.append(resultsItem);
45
+ });
46
+
47
+ searchResultsContainer.classList.remove('d-none');
48
+ });
49
+ } else {
50
+ response.json().then((json) => responseErrorContainer.innerText = json.message)
51
+ .catch(() => responseErrorContainer.innerText = 'An unepxected error occurred');
52
+ }
53
+ };
54
+
55
+ window.addEventListener('DOMContentLoaded', () => {
56
+ document.querySelectorAll('[data-field-type="text-search"]').forEach((element) => {
57
+ const id = element.id;
58
+ const hiddenId = `${id}-hidden-value`;
59
+ const formId = element.dataset.formId;
60
+ const form = document.querySelector(`#${formId}`);
61
+ const model = element.dataset.searchModel;
62
+ const attributes = tryParseJSON(element.dataset.searchAttributes, []);
63
+ const value = element.dataset.searchValue;
64
+ const token = ActiveElement.getAntiCsrfToken();
65
+ const hiddenInput = cloneElement('hidden-input');
66
+ const responseErrorContainer = cloneElement('response-error');
67
+ const searchResultsContainer = cloneElement('results');
68
+ const spinner = cloneElement('spinner');
69
+ const clearButton = cloneElement('clear-button');
70
+ document.addEventListener('click', () => {
71
+ searchResultsContainer.classList.add('d-none');
72
+ });
73
+
74
+ clearButton.addEventListener('click', () => {
75
+ element.value = '';
76
+ hiddenInput.value = '';
77
+ responseErrorContainer.innerText = '';
78
+ spinner.classList.add('invisible');
79
+ clearButton.classList.add('invisible');
80
+ });
81
+
82
+ element.addEventListener('keyup', () => {
83
+ const query = element.value;
84
+ const requestId = crypto.randomUUID();
85
+ lastRequestId = requestId;
86
+
87
+ clearButton.classList.add('invisible');
88
+ spinner.classList.remove('invisible');
89
+ hiddenInput.value = query;
90
+ searchResultsContainer.classList.add('d-none');
91
+
92
+ if (!query || query.length < 3) {
93
+ spinner.classList.add('invisible');
94
+ return;
95
+ }
96
+
97
+ searchResultsContainer.replaceChildren();
98
+
99
+ fetch(
100
+ '/_text_search/',
101
+ {
102
+ method: 'POST',
103
+ headers: { 'Content-Type': 'application/json' },
104
+ body: JSON.stringify({
105
+ request_id: requestId,
106
+ model,
107
+ value,
108
+ attributes,
109
+ query,
110
+ [token.param]: token.value,
111
+ }),
112
+ }
113
+ ).then((response) => processResponse(
114
+ { element, response, spinner, clearButton, hiddenInput, searchResultsContainer, responseErrorContainer }
115
+ ));
116
+ });
117
+
118
+ hiddenInput.name = element.name;
119
+ if (element.value) hiddenInput.value = element.value;
120
+ form.append(hiddenInput);
121
+ element.parentElement.append(searchResultsContainer);
122
+ element.parentElement.append(clearButton);
123
+ element.parentElement.append(spinner);
124
+ element.parentElement.append(responseErrorContainer);
125
+ });
126
+ });
127
+ })();
@@ -0,0 +1,40 @@
1
+ (() => {
2
+ const cloneElement = (id) => ActiveElement.cloneElement('secret', id);
3
+
4
+ window.addEventListener('DOMContentLoaded', () => {
5
+ document.querySelectorAll('span[data-field-type="secret"]').forEach((element) => {
6
+ const secret = element.dataset.secret;
7
+ const showButton = cloneElement('show-button');
8
+ const hideButton = cloneElement('hide-button');
9
+ const content = cloneElement('content');
10
+ const placeholder = secret.replace(/./g, '*');
11
+
12
+ hideButton.classList.add('d-none');
13
+ content.classList.add('font-monospace');
14
+ content.classList.add('text-secondary');
15
+ content.innerText = placeholder;
16
+
17
+ showButton.addEventListener('click', () => {
18
+ showButton.classList.add('d-none');
19
+ hideButton.classList.remove('d-none');
20
+ content.classList.remove('text-secondary');
21
+ content.innerText = secret;
22
+
23
+ return false;
24
+ });
25
+
26
+ hideButton.addEventListener('click', () => {
27
+ showButton.classList.remove('d-none');
28
+ hideButton.classList.add('d-none');
29
+ content.classList.add('text-secondary');
30
+ content.innerText = placeholder;
31
+
32
+ return false;
33
+ });
34
+
35
+ element.append(content);
36
+ element.append(showButton);
37
+ element.append(hideButton);
38
+ });
39
+ });
40
+ })();
@@ -0,0 +1,36 @@
1
+ (() => {
2
+ const generateId = () => {
3
+ ActiveElement._id += 1;
4
+
5
+ return `active-element-element-${ActiveElement._id}`;
6
+ };
7
+
8
+ const getAntiCsrfToken = () => {
9
+ const param = document.querySelector('meta[name="csrf-param"]').content;
10
+ const value = document.querySelector('meta[name="csrf-token"]').content;
11
+
12
+ return { param, value };
13
+ };
14
+
15
+ const cloneElement = (category, id) => {
16
+ const element = document.querySelector(`#${category}-templates`)
17
+ .querySelector(`#${category}-${id}-template`)
18
+ .cloneNode(true);
19
+ element.id = ActiveElement.generateId();
20
+ return element;
21
+ };
22
+
23
+ const ActiveElement = {
24
+ log: (message) => { console.log(`[ActiveElement] ${message}`); },
25
+ _id: 0,
26
+ generateId,
27
+ getAntiCsrfToken,
28
+ cloneElement,
29
+ components: {},
30
+ jsonData: {},
31
+ };
32
+
33
+ window.ActiveElement = ActiveElement;
34
+ })();
35
+
36
+ ActiveElement.log('Initialized');
@@ -0,0 +1,42 @@
1
+ (() => {
2
+ const cloneElement = (id) => ActiveElement.cloneElement('theme', id);
3
+
4
+ window.addEventListener('DOMContentLoaded', () => {
5
+ const themeSelect = document.querySelector('#theme-select');
6
+
7
+ const setTheme = (theme) => {
8
+ const themeSelectButtons = themeSelect.children;
9
+
10
+ Object.entries(themeSelectButtons).forEach(([_, element]) => {
11
+ if (element.dataset.themeSwitchTo === theme) {
12
+ element.classList.add('d-none');
13
+ } else {
14
+ element.classList.remove('d-none');
15
+ }
16
+ });
17
+
18
+ localStorage.setItem('active_element-theme', theme);
19
+ document.querySelector('html').dataset.bsTheme = theme;
20
+ };
21
+
22
+
23
+ const initTheme = () => {
24
+ const theme = localStorage.getItem('active_element-theme') || 'light';
25
+ const themeSelectButtons = document.querySelector('#theme-select-buttons').children;
26
+
27
+ Object.entries(themeSelectButtons).forEach(([_, element]) => {
28
+ themeSelect.append(element);
29
+
30
+ element.addEventListener('click', (ev) => {
31
+ event.stopPropagation();
32
+ setTheme(element.dataset.themeSwitchTo);
33
+ return false;
34
+ });
35
+ });
36
+
37
+ setTheme(theme);
38
+ };
39
+
40
+ initTheme();
41
+ });
42
+ })();