solara 0.4.0 → 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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/solara/lib/.DS_Store +0 -0
  3. data/solara/lib/core/.DS_Store +0 -0
  4. data/solara/lib/core/brands/brand_switcher.rb +58 -1
  5. data/solara/lib/core/dashboard/brand/BrandDetail.js +34 -2
  6. data/solara/lib/core/dashboard/brand/BrandDetailController.js +23 -233
  7. data/solara/lib/core/dashboard/brand/BrandDetailModel.js +13 -5
  8. data/solara/lib/core/dashboard/brand/BrandDetailView.js +16 -200
  9. data/solara/lib/core/dashboard/brand/SectionsFormManager.js +232 -0
  10. data/solara/lib/core/dashboard/brand/brand.html +187 -177
  11. data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +2 -5
  12. data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +36 -133
  13. data/solara/lib/core/dashboard/brands/Brands.js +31 -0
  14. data/solara/lib/core/dashboard/brands/BrandsController.js +0 -5
  15. data/solara/lib/core/dashboard/brands/BrandsView.js +2 -2
  16. data/solara/lib/core/dashboard/brands/brands.html +71 -52
  17. data/solara/lib/core/dashboard/component/AliasesBottomSheet.js +6 -6
  18. data/solara/lib/core/dashboard/component/BrandOptionsBottomSheet.js +4 -4
  19. data/solara/lib/core/dashboard/component/ConfirmationDialog.js +15 -10
  20. data/solara/lib/core/dashboard/component/EditJsonSheet.js +160 -0
  21. data/solara/lib/core/dashboard/component/MessageBottomSheet.js +5 -5
  22. data/solara/lib/core/dashboard/component/OnboardBrandBottomSheet.js +5 -3
  23. data/solara/lib/core/dashboard/handler/base_handler.rb +1 -0
  24. data/solara/lib/core/dashboard/handler/edit_section_handler.rb +1 -5
  25. data/solara/lib/core/doctor/schema/brand_configurations.json +0 -8
  26. data/solara/lib/core/doctor/schema/platform/global/resources_manifest.json +30 -0
  27. data/solara/lib/core/doctor/schema/platform/json_manifest.json +57 -0
  28. data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +35 -1
  29. data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +30 -1
  30. data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +35 -1
  31. data/solara/lib/core/doctor/validator/template/template_validator.rb +9 -9
  32. data/solara/lib/core/scripts/brand_config_manager.rb +1 -1
  33. data/solara/lib/core/scripts/brand_configurations_manager.rb +41 -0
  34. data/solara/lib/core/scripts/code_generator.rb +342 -118
  35. data/solara/lib/core/scripts/file_path.rb +21 -1
  36. data/solara/lib/core/scripts/gitignore_manager.rb +11 -3
  37. data/solara/lib/core/scripts/json_manifest_processor.rb +95 -0
  38. data/solara/lib/core/scripts/platform/ios/infoplist_string_catalog_manager.rb +11 -1
  39. data/solara/lib/core/scripts/resource_manifest_processor.rb +151 -0
  40. data/solara/lib/core/scripts/solara_status_manager.rb +1 -1
  41. data/solara/lib/core/scripts/theme_generator.rb +21 -242
  42. data/solara/lib/core/solara_configurator.rb +1 -1
  43. data/solara/lib/core/template/brands/global/resources_manifest.json +10 -0
  44. data/solara/lib/core/template/brands/json/Json-Manifest.md +61 -0
  45. data/solara/lib/core/template/brands/json/json_manifest.json +18 -0
  46. data/solara/lib/core/template/brands/shared/theme.json +213 -29
  47. data/solara/lib/core/template/config/android_template_config.json +50 -0
  48. data/solara/lib/core/template/config/flutter_template_config.json +35 -0
  49. data/solara/lib/core/template/config/ios_template_config.json +50 -0
  50. data/solara/lib/core/template/configurations.json +46 -0
  51. data/solara/lib/core/template/project_template_generator.rb +2 -0
  52. data/solara/lib/solara/version.rb +1 -1
  53. data/solara/lib/solara.rb +19 -0
  54. metadata +13 -4
  55. data/solara/lib/core/dashboard/component/AddFieldSheet.js +0 -175
  56. data/solara/lib/core/dashboard/handler/brand_configurations_manager.rb +0 -73
@@ -1,6 +1,6 @@
1
1
  import {DataSource} from './BrandDetailModel.js';
2
+ import SectionsFormManager from './SectionsFormManager.js';
2
3
  import '../component/OnboardBrandBottomSheet.js';
3
- import '../component/AddFieldSheet.js';
4
4
  import '../component/ConfirmationDialog.js';
5
5
  import '../component/MessageBottomSheet.js';
6
6
 
@@ -15,7 +15,6 @@ class BrandDetailView {
15
15
  this.addBrandContainer = document.getElementById('add-brand-container');
16
16
 
17
17
  this.sectionsContainer = document.getElementById('sections');
18
- this.addFieldSheet = document.getElementById('addFieldSheet');
19
18
  this.confirmationDialog = document.getElementById('confirmationDialog');
20
19
  this.messageBottomSheet = document.getElementById('messageBottomSheet');
21
20
 
@@ -24,8 +23,9 @@ class BrandDetailView {
24
23
  this.addNewBrandBtn = document.getElementById('newBrandBtn');
25
24
  this.exportBrandBtn = document.getElementById('exportBrandBtn');
26
25
  this.allBrandsButton = document.getElementById('allBrandsButton');
27
-
26
+ this.syncBrandButton = document.getElementById('syncBrandButton');
28
27
  this.onboardSheet = document.getElementById('onboardBottomSheet');
28
+ this.sectionsFormManager = new SectionsFormManager();
29
29
 
30
30
  this.initializeApp();
31
31
  }
@@ -67,199 +67,12 @@ class BrandDetailView {
67
67
  }
68
68
  }
69
69
 
70
- createSection(key, name, inputType) {
71
- const section = document.createElement('div');
72
- section.className = 'section';
73
-
74
- section.dataset.key = key
75
- section.dataset.name = name
76
- section.dataset.inputType = inputType
77
-
78
- const titleContainer = document.createElement('div');
79
- titleContainer.className = 'section-title-container';
80
-
81
- const title = document.createElement('h2');
82
- title.textContent = name;
83
- titleContainer.appendChild(title);
84
-
85
- const subtitleElement = document.createElement('p');
86
- subtitleElement.className = 'section-subtitle';
87
- subtitleElement.textContent = key;
88
- titleContainer.appendChild(subtitleElement);
89
-
90
- section.appendChild(titleContainer);
91
-
92
- return section;
93
- }
94
-
95
- populateJsonFields(data, container, content, inputType, level = 0) {
96
- container.dataset.key = data.key
97
- container.dataset.level = `${level}`
98
-
99
- for (const [key, value] of Object.entries(content)) {
100
- if (Array.isArray(value)) {
101
- this.populateJsonArray(key, value, data, container, content, inputType, level)
102
- continue
103
- }
104
-
105
- if (value !== null && typeof value === 'object') {
106
- this.populateJsonObject(key, value, data, container, content, inputType, level)
107
- continue
108
- }
109
-
110
- const fieldElement = this.createJsonField(data, key, value, inputType, level)
111
- container.appendChild(fieldElement);
112
- }
113
- }
114
-
115
- populateJsonArray(key, value, data, container, content, inputType, level) {
116
- const arrayContainer = document.createElement('div');
117
- arrayContainer.className = 'json-array';
118
- arrayContainer.classList.add(`json-array-${level}`);
119
-
120
- const labelContainer = document.createElement('div');
121
- labelContainer.className = 'json-array-label-group';
122
- const label = document.createElement('label');
123
- label.textContent = key;
124
- labelContainer.appendChild(label);
125
-
126
- // TODO: to be implemented later
127
- if (false) {
128
- const addButton = document.createElement('button');
129
- addButton.className = 'add-array-item';
130
- addButton.textContent = '+';
131
- let lastItemIndex = value.length - 1
132
- addButton.addEventListener('click', () => {
133
- const itemContainer = document.createElement('div');
134
- itemContainer.className = 'json-array-item';
135
-
136
- lastItemIndex += 1
137
- let fieldKey = `${key}[${lastItemIndex}]`
138
-
139
- const indexed = container.querySelectorAll(`.json-array-item-indexed-${level}`).length !== 0;
140
- if (indexed) {
141
- fieldKey = lastItemIndex
142
- }
143
- const field = this.createJsonField(data, fieldKey, '', inputType, level + 1)
144
- field.classList.add(`json-array-item-${level}`);
145
- itemContainer.appendChild(field);
146
-
147
- arrayContainer.insertBefore(itemContainer, arrayContainer.lastElementChild);
148
-
149
- this.onSectionChanged(data, container.closest('.section'));
150
- });
151
- }
152
- arrayContainer.appendChild(labelContainer);
153
-
154
- value.forEach((item, index) => {
155
- const itemContainer = document.createElement('div');
156
- itemContainer.className = 'json-array-item';
157
- itemContainer.classList.add(`json-array-item-${level}`);
158
- if (typeof item === 'object' && item !== null) {
159
- this.populateJsonFields(data, itemContainer, item, inputType, level + 1);
160
- } else {
161
- itemContainer.dataset.level = `${level + 1}`
162
- const field = this.createJsonField(data, `${index}`, item, inputType, level + 1)
163
- itemContainer.classList.add(`json-array-item-indexed-${level}`);
164
- itemContainer.appendChild(field);
165
- }
166
- arrayContainer.appendChild(itemContainer);
167
- });
168
-
169
- // TODO: to be implemented later
170
- // arrayContainer.appendChild(addButton);
171
- container.appendChild(arrayContainer);
172
- }
173
-
174
- populateJsonObject(key, value, data, container, content, inputType, level) {
175
- const objectContainer = document.createElement('div');
176
- objectContainer.className = 'json-object';
177
- objectContainer.classList.add(`json-object-${level}`);
178
- const objectLabel = document.createElement('label');
179
- objectLabel.className = 'json-object-title';
180
- objectLabel.textContent = key;
181
- objectContainer.appendChild(objectLabel);
182
-
183
- this.populateJsonFields(data, objectContainer, value, inputType, level + 1);
184
-
185
- container.appendChild(objectContainer);
186
- }
187
-
188
- createJsonField(data, key, value, inputType, level) {
189
- const fieldInputType = typeof value === 'boolean' ? 'boolean' : inputType;
190
- const container = document.createElement('div');
191
- container.className = 'input-group';
192
- const label = document.createElement('label');
193
- label.textContent = key;
194
- container.appendChild(label);
195
-
196
- const inputWrapper = document.createElement('div');
197
- inputWrapper.className = 'input-wrapper';
198
-
199
- if (fieldInputType === 'boolean') {
200
- const checkbox = document.createElement('input');
201
- checkbox.type = 'checkbox';
202
- checkbox.id = key;
203
- checkbox.checked = value;
204
-
205
- const checkboxLabel = document.createElement('label');
206
- checkboxLabel.className = 'checkbox-label';
207
- checkboxLabel.htmlFor = key;
208
- checkboxLabel.textContent = value ? 'True' : 'False';
209
-
210
- checkbox.addEventListener('change', () => {
211
- checkboxLabel.textContent = checkbox.checked ? 'True' : 'False';
212
- this.onSectionChanged(data, container.closest('.section'));
213
- });
214
-
215
- inputWrapper.appendChild(checkbox);
216
- inputWrapper.appendChild(checkboxLabel);
217
- } else {
218
- const input = document.createElement('input');
219
- input.type = fieldInputType;
220
- input.id = key;
221
-
222
- console.log(value)
223
- if (fieldInputType === 'color') {
224
- input.value = value.startsWith('#') ? value : `#${value.substring(4)}`;
225
- } else {
226
- input.value = value;
227
- }
228
-
229
- inputWrapper.appendChild(input);
230
- }
231
-
232
- const deleteIcon = document.createElement('span');
233
- deleteIcon.className = 'delete-icon';
234
- deleteIcon.textContent = '×';
235
- deleteIcon.onclick = () => this.onDeleteField(data, container);
236
- inputWrapper.appendChild(deleteIcon);
237
-
238
- container.appendChild(inputWrapper);
239
-
240
- container.addEventListener('change', () => this.onSectionChanged(data, container.closest('.section')));
241
-
242
- container.classList.add(`json-field-${level}`);
243
-
244
- return container;
245
- }
246
-
247
- showAddFieldForm(data, sectionElement, inputType) {
248
- this.addFieldSheet.show(inputType, (name, value) => {
249
- const newField = this.createJsonField(data, name, value, inputType, 0);
250
- sectionElement.insertBefore(newField, sectionElement.lastElementChild);
251
- this.onSectionChanged(data, sectionElement);
252
- })
253
- }
254
-
255
70
  showConfirmationDialog(message, onConfirm) {
256
71
  this.confirmationDialog.showDialog(message, onConfirm);
257
72
  }
258
73
 
259
- showApplyChangesButton() {
260
- const applyChangesButton = document.getElementById('applyChangesButton');
261
- applyChangesButton.style.display = 'block';
262
- this.header.style.backgroundColor = '#ff4136';
74
+ setupSyncBrandButton(color) {
75
+ this.syncBrandButton.style.display = 'block';
263
76
  }
264
77
 
265
78
  showSwitchButton() {
@@ -276,14 +89,6 @@ class BrandDetailView {
276
89
  this.messageBottomSheet.showMessage(message);
277
90
  }
278
91
 
279
- setOnSectionChangedHandler(handler) {
280
- this.onSectionChanged = handler;
281
- }
282
-
283
- setOnDeleteFieldHandler(handler) {
284
- this.onDeleteField = handler;
285
- }
286
-
287
92
  showOnboardBrandForm(onSubmit) {
288
93
  this.onboardSheet.show('Brand Details', 'Add Brand', onSubmit);
289
94
  }
@@ -309,6 +114,17 @@ class BrandDetailView {
309
114
  this.onboardSheet.hide();
310
115
  }
311
116
 
117
+ async toast(message) {
118
+ const toastElement = document.getElementById('toast');
119
+ toastElement.textContent = message;
120
+ toastElement.style.display = 'block';
121
+
122
+ setTimeout(() => {
123
+ toastElement.style.display = 'none';
124
+ }, 3000);
125
+ }
126
+
127
+
312
128
  }
313
129
 
314
130
  export default BrandDetailView;
@@ -0,0 +1,232 @@
1
+ import '../component/EditJsonSheet.js';
2
+
3
+ class SectionsFormManager {
4
+
5
+ constructor() {
6
+ this.sections = [];
7
+ this.sectionsContainer = document.getElementById('sections');
8
+ }
9
+
10
+ display(sections, onChange) {
11
+ this.sections = sections.map((section) => {
12
+ return new SectionItemManager(
13
+ section,
14
+ this.createSection(section),
15
+ onChange)
16
+ })
17
+ this.sections.forEach((item => {
18
+ item.displayJSONCards()
19
+ }))
20
+ }
21
+
22
+ createSection(section) {
23
+ const sectionElement = document.createElement('div');
24
+ sectionElement.className = 'section';
25
+
26
+ sectionElement.dataset.key = section.key
27
+ sectionElement.dataset.name = section.name
28
+
29
+ const titleContainer = document.createElement('div');
30
+ titleContainer.className = 'section-title-container';
31
+
32
+ const title = document.createElement('h2');
33
+ title.className = "section-title";
34
+ title.textContent = section.name;
35
+ titleContainer.appendChild(title);
36
+
37
+ sectionElement.appendChild(titleContainer);
38
+
39
+ sectionElement.id = section.key;
40
+
41
+ this.sectionsContainer.appendChild(sectionElement);
42
+
43
+ return sectionElement
44
+ }
45
+
46
+ data() {
47
+ const data = this.sections.map((section) => {
48
+ return section.section
49
+ })
50
+ return Array.from(data);
51
+ }
52
+
53
+ }
54
+
55
+ class SectionItemManager {
56
+ constructor(section, container, onChange) {
57
+ this.section = section
58
+ this.container = container
59
+ this.onChange = onChange
60
+ this.editJsonSheet = document.getElementById('editJsonSheet');
61
+ }
62
+
63
+ displayJSONCards() {
64
+ let cardContent = this.container.querySelector('.card-content')
65
+ if (cardContent !== null) this.container.innerHTML = ''
66
+ this.container.appendChild(this.createCard(this.section.content, 'root', null, this.section.key));
67
+ }
68
+
69
+ createCard(obj, key, parent, cardTitle) {
70
+ const card = document.createElement('div');
71
+ card.className = 'card';
72
+
73
+ const header = document.createElement('div');
74
+ header.className = 'card-header';
75
+ header.textContent = key === 'root' ? cardTitle : key;
76
+ header.onclick = () => {
77
+ if (key !== 'root') {
78
+ this.editKey(parent, key)
79
+ return
80
+ }
81
+ this.editJsonSheet.show(
82
+ JSON.stringify(this.section.content, null, 2),
83
+ cardTitle,
84
+ (value) => {
85
+ this.section.content = value
86
+ this.displayJSONCards()
87
+ this.notifyChange()
88
+ })
89
+ };
90
+
91
+ const actions = document.createElement('div');
92
+ actions.className = 'card-actions';
93
+
94
+ const addBtn = document.createElement('button');
95
+ addBtn.className = 'add-property-btn';
96
+ addBtn.innerHTML = '<i class="fas fa-plus"></i>';
97
+ addBtn.onclick = () => this.addProperty(obj);
98
+ actions.appendChild(addBtn);
99
+
100
+ if (key !== 'root') {
101
+ const deleteCardBtn = document.createElement('button');
102
+ deleteCardBtn.className = 'delete-btn';
103
+ deleteCardBtn.innerHTML = '<i class="fas fa-times"></i>';
104
+ deleteCardBtn.onclick = () => this.confirmDeleteProperty(parent, key);
105
+ actions.appendChild(deleteCardBtn);
106
+ }
107
+
108
+ header.appendChild(actions);
109
+ card.appendChild(header);
110
+
111
+ const content = document.createElement('div');
112
+ content.className = 'card-content';
113
+
114
+ const isArray = Array.isArray(obj)
115
+
116
+ for (const [k, v] of Object.entries(obj)) {
117
+ const item = document.createElement('div');
118
+
119
+ const cardValueContainer = document.createElement('div');
120
+ cardValueContainer.className = 'card-value-container';
121
+ item.appendChild(cardValueContainer);
122
+
123
+ const itemKey = document.createElement('span');
124
+ itemKey.className = 'card-key';
125
+ itemKey.onclick = () => {
126
+ if (isArray) return
127
+ this.editKey(obj, k)
128
+ };
129
+ cardValueContainer.appendChild(itemKey);
130
+
131
+ if (typeof v === 'object' && v !== null) {
132
+ item.appendChild(this.createCard(v, k, obj, null));
133
+ itemKey.textContent = isArray ? `${key}[${k}]` : ''
134
+ } else {
135
+ item.className = 'card-item';
136
+ itemKey.textContent = k.replace(/_/g, ' ')
137
+
138
+ if (this.isColorValue(v)) {
139
+ const itemValue = document.createElement('input');
140
+ itemValue.type = 'color';
141
+ itemValue.className = 'card-value';
142
+ itemValue.value = v;
143
+ itemValue.onchange = () => this.updateValue(obj, k, itemValue.value);
144
+ cardValueContainer.appendChild(itemValue);
145
+ } else {
146
+ const itemValue = document.createElement('textarea');
147
+ itemValue.className = 'card-value';
148
+ itemValue.value = v;
149
+ itemValue.onchange = () => this.updateValue(obj, k, itemValue.value);
150
+ cardValueContainer.appendChild(itemValue);
151
+ }
152
+
153
+ const deleteBtn = document.createElement('button');
154
+ deleteBtn.className = 'delete-btn';
155
+ deleteBtn.innerHTML = '<i class="fas fa-times"></i>';
156
+ deleteBtn.onclick = () => this.confirmDeleteProperty(obj, k);
157
+ cardValueContainer.appendChild(deleteBtn);
158
+ }
159
+
160
+ content.appendChild(item);
161
+ }
162
+
163
+ card.appendChild(content);
164
+ return card;
165
+ }
166
+
167
+ isColorValue(value) {
168
+ // Check if the value is a valid color (hex with opacity, RGBA, or RGB)
169
+ const hexPattern = /^#([0-9A-F]{3}){1,2}([0-9A-F]{2})?$/i; // 3, 6, or 8 hex digits
170
+ const rgbaPattern = /^rgba?\(\s*(\d{1,3}\s*,\s*){2}\d{1,3}\s*,?\s*(0|1|0?\.\d+|1?\.\d+)\s*\)$/;
171
+
172
+ return hexPattern.test(value) || rgbaPattern.test(value);
173
+ }
174
+
175
+ editKey(obj, oldKey) {
176
+ const newKey = prompt('Edit property name:', oldKey);
177
+ if (newKey && newKey !== oldKey) {
178
+ obj[newKey] = obj[oldKey];
179
+ delete obj[oldKey];
180
+ this.displayJSONCards();
181
+ }
182
+ this.notifyChange()
183
+ }
184
+
185
+ updateValue(obj, key, value) {
186
+ try {
187
+ obj[key] = JSON.parse(value);
188
+ } catch {
189
+ obj[key] = value;
190
+ }
191
+ this.displayJSONCards();
192
+ this.notifyChange()
193
+ }
194
+
195
+ confirmDeleteProperty(obj, key) {
196
+ const confirmationDialog = document.getElementById('confirmationDialog');
197
+ confirmationDialog.showDialog(`Are you sure you need to delete: ${key}?`,
198
+ async () => {
199
+ this.deleteProperty(obj, key)
200
+ });
201
+ }
202
+
203
+ deleteProperty(obj, key) {
204
+ if (Array.isArray(obj)) {
205
+ obj.splice(key, 1);
206
+ } else {
207
+ delete obj[key];
208
+ }
209
+ this.displayJSONCards();
210
+ this.notifyChange()
211
+ }
212
+
213
+ addProperty(obj) {
214
+ if (Array.isArray(obj)) {
215
+ obj.push('');
216
+ } else {
217
+ const key = prompt('Enter new property name:');
218
+ if (key) {
219
+ obj[key] = '';
220
+ }
221
+ }
222
+ this.displayJSONCards();
223
+ this.notifyChange()
224
+ }
225
+
226
+ notifyChange() {
227
+ this.onChange(this.section, this.container)
228
+ }
229
+ }
230
+
231
+
232
+ export default SectionsFormManager;