solara 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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;