solara 0.5.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) 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_onboarder.rb +29 -25
  5. data/solara/lib/core/brands/brand_switcher.rb +59 -24
  6. data/solara/lib/core/dashboard/brand/BrandDetailController.js +6 -3
  7. data/solara/lib/core/dashboard/brand/BrandDetailModel.js +1 -0
  8. data/solara/lib/core/dashboard/brand/SectionsFormManager.js +76 -15
  9. data/solara/lib/core/dashboard/brand/brand.html +47 -8
  10. data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +2 -2
  11. data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +19 -3
  12. data/solara/lib/core/dashboard/component/OnboardBrandBottomSheet.js +4 -0
  13. data/solara/lib/core/dashboard/handler/edit_section_handler.rb +5 -17
  14. data/solara/lib/core/dashboard/handler/onboard_brand_handler.rb +0 -15
  15. data/solara/lib/core/doctor/schema/brand_configurations.json +2 -2
  16. data/solara/lib/core/doctor/schema/platform/json_manifest.json +20 -38
  17. data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +3 -3
  18. data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +2 -2
  19. data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +3 -3
  20. data/solara/lib/core/scripts/brand_config_updater.rb +29 -0
  21. data/solara/lib/core/scripts/brand_configurations_manager.rb +76 -14
  22. data/solara/lib/core/scripts/brand_importer.rb +9 -20
  23. data/solara/lib/core/scripts/code_generator.rb +1 -1
  24. data/solara/lib/core/scripts/file_manager.rb +11 -15
  25. data/solara/lib/core/scripts/gitignore_manager.rb +3 -4
  26. data/solara/lib/core/scripts/json_manifest_processor.rb +86 -45
  27. data/solara/lib/core/scripts/resource_manifest_processor.rb +11 -10
  28. data/solara/lib/core/scripts/string_case.rb +18 -0
  29. data/solara/lib/core/template/.DS_Store +0 -0
  30. data/solara/lib/core/template/brands/json/Json-Manifest.md +8 -10
  31. data/solara/lib/core/template/brands/json/json_manifest.json +9 -11
  32. data/solara/lib/core/template/config/android_template_config.json +6 -6
  33. data/solara/lib/core/template/config/flutter_template_config.json +4 -4
  34. data/solara/lib/core/template/config/ios_template_config.json +6 -6
  35. data/solara/lib/core/template/configurations.json +34 -21
  36. data/solara/lib/core/template/project_template_generator.rb +119 -13
  37. data/solara/lib/solara/version.rb +1 -1
  38. data/solara/lib/solara_manager.rb +25 -13
  39. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 714797cd507e354e49c4a1981a516f6ad3e659797ba40b6040c7e7a6f30f11aa
4
- data.tar.gz: 3b3d4ed6c116abea9dd15c2db4b5df308320649be4381623e3c7a3c863ef0385
3
+ metadata.gz: ee9572ad8162826e8ebd8ed6b9f5e6a0ba5c2ef23ecec56ed610206becc3e80f
4
+ data.tar.gz: d0cfbf2ba68d160fdf531f3148e216662063d2a9182c341ea22e1266e283bcf5
5
5
  SHA512:
6
- metadata.gz: b4c46c3f893d95eddbde530e7d83fe340ce36698974880073b3517779bb8e39b31813863c13f7743e821936b814e6d7f5d42337c10b3ef37a4cd6767732f5a20
7
- data.tar.gz: 750e7851fa9584bb85b00b5f01e48ea246892914f1bbfafeb7a2b18db8e138d0da5f8fd2a2253434bed1dd6b6d9b0c6911dcaec908714e96ff69c894653490b8
6
+ metadata.gz: 7e2e2c181701aa9af6099694beedc3f68e926446e8a1d67b4841e66b5ecf1da8fad76c9b479a52738f0bf7a48551328b7058c0bbafcc1574a68b9b388b37035a
7
+ data.tar.gz: e3e2d47d57ed66593435bb6294013c99711177bfadb4ae25ea74d2ebb1d511abc0eb98dd7fa9f4579106cf7ec26cf76393294b7d4fd9b6c89aa0c679343ef8e3
data/solara/lib/.DS_Store CHANGED
Binary file
Binary file
@@ -5,43 +5,47 @@ Dir[File.expand_path('platform/ios/*.rb', __dir__)].each { |file| require_relati
5
5
  Dir[File.expand_path('platform/flutter/*.rb', __dir__)].each { |file| require_relative file }
6
6
 
7
7
  class BrandOnboarder
8
- def initialize(brand_key, brand_name, clone_brand_key: nil)
9
- @brand_key = brand_key
10
- @brand_name = brand_name
11
- @clone_brand_key = clone_brand_key
12
- end
13
8
 
14
- def onboard
15
- if @clone_brand_key.nil? || @clone_brand_key.empty?
16
- generate_brand_template
9
+ def onboard(brand_key, brand_name, clone_brand_key: nil)
10
+ if clone_brand_key.nil? || clone_brand_key.strip.empty?
11
+ generate_from_template(brand_key)
17
12
  else
18
- clone_brand
13
+ clone_brand(brand_key, clone_brand_key: clone_brand_key)
19
14
  end
20
- add_to_brands_list
15
+ add_to_brands_list(brand_key, brand_name)
21
16
  end
22
17
 
23
- def generate_brand_template
24
- Solara.logger.debug("Onboarding #{@brand_key} from template.")
25
-
26
- template_dir = FilePath.template_brands
27
- target_dir = FilePath.brand(@brand_key)
28
- config_file = FilePath.template_config
29
-
30
- generator = ProjectTemplateGenerator.new(template_dir, target_dir, config_file)
31
- generator.create_project
18
+ def sync_with_template(brand_key)
19
+ generator = template_generator(brand_key)
20
+ generator.sync_with_template
32
21
  end
33
22
 
34
- def clone_brand
35
- Solara.logger.debug("Cloning #{@clone_brand_key} to #{@brand_key}")
36
- source = FilePath.brand(@clone_brand_key)
37
- destination = FilePath.brand(@brand_key)
23
+ def clone_brand(brand_key, clone_brand_key:)
24
+ Solara.logger.debug("Cloning #{clone_brand_key} to #{brand_key}")
25
+ source = FilePath.brand(clone_brand_key)
26
+ destination = FilePath.brand(brand_key)
38
27
 
39
28
  FileManager.delete_if_exists(destination)
40
29
  FolderCopier.new(source, destination).copy
41
30
  end
42
31
 
43
- def add_to_brands_list
44
- BrandsManager.instance.add_brand(@brand_name, @brand_key)
32
+ def add_to_brands_list(brand_key, brand_name)
33
+ BrandsManager.instance.add_brand(brand_name, brand_key)
34
+ end
35
+
36
+ def generate_from_template(brand_key)
37
+ Solara.logger.debug("Onboarding #{brand_key} from template.")
38
+
39
+ generator = template_generator(brand_key)
40
+ generator.create_project
41
+ end
42
+
43
+ def template_generator(brand_key)
44
+ template_dir = FilePath.template_brands
45
+ target_dir = FilePath.brand(brand_key)
46
+ config_file = FilePath.template_config
47
+
48
+ ProjectTemplateGenerator.new(template_dir, target_dir, config_file)
45
49
  end
46
50
 
47
51
  end
@@ -16,6 +16,7 @@ class BrandSwitcher
16
16
  def start
17
17
  Solara.logger.header("Switching to #{@brand_key}")
18
18
 
19
+ BrandOnboarder.new.sync_with_template(@brand_key)
19
20
  @health_checker.check_health
20
21
  BrandsManager.instance.save_current_brand(@brand_key)
21
22
  @artifacts_switcher.switch
@@ -30,7 +31,7 @@ class BrandSwitcher
30
31
  def switch
31
32
  BrandFontSwitcher.new(@brand_key).switch
32
33
 
33
- ResourceManifestSwitcher.new(@brand_key).switch
34
+ ResourceManifestSwitcher.new(@brand_key, ignore_health_check: @ignore_health_check).switch
34
35
  JsonManifestSwitcher.new(@brand_key).switch
35
36
 
36
37
  case @platform
@@ -51,13 +52,14 @@ class BrandSwitcher
51
52
  end
52
53
 
53
54
  class ResourceManifestSwitcher
54
- def initialize(brand_key)
55
+ def initialize(brand_key, ignore_health_check:)
55
56
  @brand_key = brand_key
57
+ @ignore_health_check = ignore_health_check
56
58
  end
57
59
 
58
60
  def switch
59
61
  Solara.logger.start_step("Process resource manifest: #{FilePath.resources_manifest}")
60
- brand_resource_copier = ResourceManifestProcessor.new(@brand_key)
62
+ brand_resource_copier = ResourceManifestProcessor.new(@brand_key, ignore_health_check: @ignore_health_check)
61
63
  brand_resource_copier.copy
62
64
  Solara.logger.debug("#{@brand_key} resources copied successfully according to the manifest: #{FilePath.resources_manifest}.")
63
65
  Solara.logger.end_step("Process resource manifest: #{FilePath.resources_manifest}")
@@ -66,39 +68,72 @@ class ResourceManifestSwitcher
66
68
  end
67
69
 
68
70
  class JsonManifestSwitcher
71
+ PLATFORM_CONFIGS = {
72
+ Platform::Flutter => {
73
+ language: Language::Dart,
74
+ output_path: :flutter_lib_artifacts
75
+ },
76
+ Platform::IOS => {
77
+ language: Language::Swift,
78
+ output_path: :ios_project_root_artifacts
79
+ },
80
+ Platform::Android => {
81
+ language: Language::Kotlin,
82
+ output_path: :android_project_java_artifacts
83
+ }
84
+ }.freeze
85
+
69
86
  def initialize(brand_key)
70
87
  @brand_key = brand_key
71
- @manifest_path = FilePath.brand_json_dir(brand_key)
88
+ @brand_manifest_path = FilePath.brand_json_dir(brand_key)
89
+ @platform = SolaraSettingsManager.instance.platform
90
+ validate_inputs
72
91
  end
73
92
 
74
93
  def switch
75
- Solara.logger.start_step("Process JSON manifest: #{@manifest_path}")
94
+ with_logging do
95
+ process_manifests
96
+ end
97
+ end
76
98
 
99
+ private
77
100
 
78
- case SolaraSettingsManager.instance.platform
79
- when Platform::Flutter
80
- process_maifest(Language::Dart, FilePath.flutter_lib_artifacts)
81
- process_maifest(Language::Dart, FilePath.brand_global_json_dir)
82
- when Platform::IOS
83
- process_maifest(Language::Swift, FilePath.ios_project_root_artifacts)
84
- process_maifest(Language::Swift, FilePath.brand_global_json_dir)
85
- when Platform::Android
86
- process_maifest(Language::Kotlin, FilePath.android_project_java_artifacts )
87
- process_maifest(Language::Kotlin, FilePath.brand_global_json_dir)
88
- else
89
- raise ArgumentError, "Invalid platform: #{@platform}"
90
- end
101
+ def validate_inputs
102
+ raise ArgumentError, "Brand key cannot be nil" if @brand_key.nil?
103
+ raise ArgumentError, "Invalid platform: #{@platform}" unless PLATFORM_CONFIGS.key?(@platform)
104
+ raise ArgumentError, "Manifest path not found: #{@brand_manifest_path}" unless File.exist?(@brand_manifest_path)
105
+ end
91
106
 
92
- Solara.logger.end_step("Process JSON manifest: #{@manifest_path}")
107
+ def with_logging
108
+ log_message = "Process JSON manifest: #{@brand_manifest_path}"
109
+ Solara.logger.start_step(log_message)
110
+ yield
111
+ Solara.logger.end_step(log_message)
112
+ rescue StandardError => e
113
+ Solara.logger.error("Failed to process manifest: #{e.message}")
114
+ raise
115
+ end
116
+
117
+ def process_manifests
118
+ config = PLATFORM_CONFIGS[@platform]
119
+ output_path = FilePath.send(config[:output_path])
120
+
121
+ [
122
+ @brand_manifest_path,
123
+ FilePath.brand_global_json_dir
124
+ ].each do |manifest_path|
125
+ process_manifest(config[:language], manifest_path, output_path)
126
+ end
93
127
  end
94
128
 
95
- def process_maifest(language, output_path)
96
- processor = JsonManifestProcessor.new(
97
- @manifest_path,
129
+ def process_manifest(language, manifest_path, output_path)
130
+ JsonManifestProcessor.new(
131
+ manifest_path,
98
132
  language,
99
133
  output_path
100
- )
101
- processor.process
134
+ ).process
135
+ rescue StandardError => e
136
+ raise StandardError, "Failed to process #{manifest_path}: #{e.message}"
102
137
  end
103
138
  end
104
139
 
@@ -91,6 +91,7 @@ class BrandDetailController {
91
91
  const response = await this.model.fetchBrandDetails();
92
92
  await this.onLoadSections(response.result);
93
93
  const {isCurrentBrand, contentChanged} = await this.model.fetchCurrentBrand();
94
+ this.model.isCurrentBrand = isCurrentBrand
94
95
 
95
96
  if (isCurrentBrand) {
96
97
  this.view.setupSyncBrandButton(contentChanged ? '#ff4136' : '#4A90E2');
@@ -109,7 +110,7 @@ class BrandDetailController {
109
110
  try {
110
111
  this.view.addBrandOverlay.style.display = 'none'
111
112
  this.view.header.style.display = 'flex';
112
- this.view.updateAppNameTitle(`${configuraationsResult.brand.key} (${configuraationsResult.brand.name})`);
113
+ this.view.updateAppNameTitle(`${configuraationsResult.brand.name} - ${configuraationsResult.brand.key}`);
113
114
  await this.showSections(configuraationsResult);
114
115
  this.view.showIndex();
115
116
  } catch (error) {
@@ -157,9 +158,11 @@ class BrandDetailController {
157
158
  if (this.model.isRemote()) return
158
159
 
159
160
  try {
160
- await this.model.saveSection(container.dataset.key, section.content);
161
+ await this.model.saveSection(container.dataset.filename, section.content);
161
162
  await this.checkBrandHealth();
162
- await this.switchToBrand(true)
163
+ if (this.model.isCurrentBrand) {
164
+ await this.switchToBrand(true)
165
+ }
163
166
  } catch (error) {
164
167
  console.error('Error saving section:', error);
165
168
  alert(error.message);
@@ -10,6 +10,7 @@ class BrandDetailModel {
10
10
  constructor() {
11
11
  this.localSource = new BrandLocalSource();
12
12
  this.remoteSource = new BrandRemoteSource();
13
+ this.isCurrentBrand = false;
13
14
 
14
15
  this.brandKey = this.getQueryFromUrl('brand_key');
15
16
 
@@ -1,7 +1,6 @@
1
1
  import '../component/EditJsonSheet.js';
2
2
 
3
3
  class SectionsFormManager {
4
-
5
4
  constructor() {
6
5
  this.sections = [];
7
6
  this.sectionsContainer = document.getElementById('sections');
@@ -23,7 +22,7 @@ class SectionsFormManager {
23
22
  const sectionElement = document.createElement('div');
24
23
  sectionElement.className = 'section';
25
24
 
26
- sectionElement.dataset.key = section.key
25
+ sectionElement.dataset.filename = section.filename
27
26
  sectionElement.dataset.name = section.name
28
27
 
29
28
  const titleContainer = document.createElement('div');
@@ -36,7 +35,7 @@ class SectionsFormManager {
36
35
 
37
36
  sectionElement.appendChild(titleContainer);
38
37
 
39
- sectionElement.id = section.key;
38
+ sectionElement.id = section.filename;
40
39
 
41
40
  this.sectionsContainer.appendChild(sectionElement);
42
41
 
@@ -49,7 +48,6 @@ class SectionsFormManager {
49
48
  })
50
49
  return Array.from(data);
51
50
  }
52
-
53
51
  }
54
52
 
55
53
  class SectionItemManager {
@@ -63,7 +61,7 @@ class SectionItemManager {
63
61
  displayJSONCards() {
64
62
  let cardContent = this.container.querySelector('.card-content')
65
63
  if (cardContent !== null) this.container.innerHTML = ''
66
- this.container.appendChild(this.createCard(this.section.content, 'root', null, this.section.key));
64
+ this.container.appendChild(this.createCard(this.section.content, 'root', null, this.section.filename));
67
65
  }
68
66
 
69
67
  createCard(obj, key, parent, cardTitle) {
@@ -113,7 +111,7 @@ class SectionItemManager {
113
111
 
114
112
  const isArray = Array.isArray(obj)
115
113
 
116
- for (const [k, v] of Object.entries(obj)) {
114
+ for (const [k, v] of Object.entries(obj)) {
117
115
  const item = document.createElement('div');
118
116
 
119
117
  const cardValueContainer = document.createElement('div');
@@ -135,18 +133,62 @@ class SectionItemManager {
135
133
  item.className = 'card-item';
136
134
  itemKey.textContent = k.replace(/_/g, ' ')
137
135
 
138
- if (this.isColorValue(v)) {
136
+ if (typeof v === 'boolean') {
137
+ // Create a container for the entire boolean input
138
+ const booleanContainer = document.createElement('div');
139
+ booleanContainer.className = 'boolean-container';
140
+
141
+ const checkboxContainer = document.createElement('div');
142
+ checkboxContainer.className = 'card-value checkbox-container';
143
+
144
+ const itemValue = document.createElement('input');
145
+ itemValue.type = 'checkbox';
146
+ itemValue.className = 'card-value checkbox';
147
+ itemValue.checked = v;
148
+
149
+ const valueLabel = document.createElement('span');
150
+ valueLabel.className = 'checkbox-value';
151
+ valueLabel.textContent = v.toString();
152
+
153
+ const updateValue = () => {
154
+ const newValue = !itemValue.checked;
155
+ itemValue.checked = newValue;
156
+ valueLabel.textContent = newValue.toString();
157
+ this.updateValue(obj, k, newValue, typeof v);
158
+ };
159
+
160
+ // Add click handlers to both container and checkbox
161
+ booleanContainer.onclick = (e) => {
162
+ if (e.target !== itemValue) { // Prevent double-toggle when clicking checkbox
163
+ updateValue();
164
+ }
165
+ };
166
+
167
+ itemValue.onchange = () => {
168
+ valueLabel.textContent = itemValue.checked.toString();
169
+ this.updateValue(obj, k, itemValue.checked, typeof v);
170
+ };
171
+
172
+ checkboxContainer.appendChild(itemValue);
173
+ checkboxContainer.appendChild(valueLabel);
174
+
175
+ // Move the key inside the boolean container
176
+ booleanContainer.appendChild(itemKey);
177
+ booleanContainer.appendChild(checkboxContainer);
178
+
179
+ cardValueContainer.appendChild(booleanContainer);
180
+ } else if (this.isColorValue(v)) {
139
181
  const itemValue = document.createElement('input');
140
182
  itemValue.type = 'color';
141
183
  itemValue.className = 'card-value';
142
184
  itemValue.value = v;
143
- itemValue.onchange = () => this.updateValue(obj, k, itemValue.value);
185
+ itemValue.onchange = () => this.updateValue(obj, k, itemValue.value, typeof v);
144
186
  cardValueContainer.appendChild(itemValue);
145
187
  } else {
146
188
  const itemValue = document.createElement('textarea');
147
189
  itemValue.className = 'card-value';
148
190
  itemValue.value = v;
149
- itemValue.onchange = () => this.updateValue(obj, k, itemValue.value);
191
+ itemValue.onchange = () => this.updateValue(obj, k, itemValue.value, typeof v);
150
192
  cardValueContainer.appendChild(itemValue);
151
193
  }
152
194
 
@@ -165,8 +207,7 @@ class SectionItemManager {
165
207
  }
166
208
 
167
209
  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
210
+ const hexPattern = /^#([0-9A-F]{3}){1,2}([0-9A-F]{2})?$/i;
170
211
  const rgbaPattern = /^rgba?\(\s*(\d{1,3}\s*,\s*){2}\d{1,3}\s*,?\s*(0|1|0?\.\d+|1?\.\d+)\s*\)$/;
171
212
 
172
213
  return hexPattern.test(value) || rgbaPattern.test(value);
@@ -182,14 +223,35 @@ class SectionItemManager {
182
223
  this.notifyChange()
183
224
  }
184
225
 
185
- updateValue(obj, key, value) {
226
+ updateValue(obj, key, value, originalType) {
186
227
  try {
187
- obj[key] = JSON.parse(value);
228
+ // Handle different types based on the original value's type
229
+ switch (originalType) {
230
+ case 'string':
231
+ obj[key] = String(value);
232
+ break;
233
+ case 'number':
234
+ // If the original was a number, keep it number
235
+ if (!isNaN(value) || value === '') {
236
+ obj[key] = Number(value);
237
+ }
238
+ break;
239
+ case 'boolean':
240
+ obj[key] = value.toLowerCase() === 'true';
241
+ break;
242
+ default:
243
+ // Try to parse as JSON, fallback to string if it fails
244
+ try {
245
+ obj[key] = JSON.parse(value);
246
+ } catch {
247
+ obj[key] = value;
248
+ }
249
+ }
188
250
  } catch {
189
251
  obj[key] = value;
190
252
  }
191
253
  this.displayJSONCards();
192
- this.notifyChange()
254
+ this.notifyChange();
193
255
  }
194
256
 
195
257
  confirmDeleteProperty(obj, key) {
@@ -228,5 +290,4 @@ class SectionItemManager {
228
290
  }
229
291
  }
230
292
 
231
-
232
293
  export default SectionsFormManager;
@@ -90,7 +90,7 @@
90
90
  .right {
91
91
  width: 15%;
92
92
  position: fixed;
93
- max-height: 50%;
93
+ max-height: 95%;
94
94
  overflow-y: auto;
95
95
  padding: 7px;
96
96
  right: 0;
@@ -253,7 +253,7 @@
253
253
  #error-button {
254
254
  position: fixed;
255
255
  bottom: 14px;
256
- right: 14px;
256
+ left: 14px;
257
257
  background-color: #ff4136;
258
258
  color: white;
259
259
  border: none;
@@ -515,6 +515,45 @@
515
515
  background-color: var(--background-color);
516
516
  color: var(--text-color);
517
517
  }
518
+ .boolean-container {
519
+ flex-direction: row;
520
+ display: flex;
521
+ align-items: center;
522
+
523
+ cursor: pointer;
524
+ padding: 0px;
525
+ flex: 1;
526
+ }
527
+
528
+ .boolean-container:hover {
529
+ background-color: var(--background-color));
530
+ }
531
+
532
+ .card-value.checkbox-container {
533
+ flex-direction: row;
534
+ display: flex;
535
+ align-items: start;
536
+ gap: 0px;
537
+ padding-bottom: 10px;
538
+ margin-left: 10px;
539
+ }
540
+
541
+ .card-value.checkbox {
542
+ width: auto;
543
+ height: auto;
544
+ margin: 0;
545
+ flex: 0;
546
+ cursor: pointer;
547
+ }
548
+
549
+ .checkbox-value {
550
+ font-size: 1.2em;
551
+ color: var(--text-color);
552
+ user-select: none;
553
+ flex: 0;
554
+ margin-left: 10px;
555
+ }
556
+
518
557
  .card-actions {
519
558
  display: flex;
520
559
  align-items: center;
@@ -556,7 +595,7 @@
556
595
  <img class="loading-overlay-logo" src="../solara.png" alt="Loading Logo">
557
596
  </div>
558
597
 
559
- <div id="toast" style=" display: none; position: fixed; top: 10%; left: 50%; transform: translate(-50%, 10%); background-color: #4CAF50; color: white; padding: 16px; border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);">
598
+ <div id="toast" style=" display: none; position: fixed; top: 10%; left: 50%; transform: translate(-50%, 10%); background-color: #4CAF50; color: white; padding: 16px; border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);">
560
599
  <!-- Message will be set dynamically -->
561
600
  </div>
562
601
 
@@ -591,11 +630,11 @@
591
630
  <img src="../solara.png" alt="Solara Logo">
592
631
  <h2>Solara simplifies the management of your brand configurations, allowing you to access and update them anytime, anywhere.</h2>
593
632
  <div class="button-message">You can select a JSON file containing brand configurations that were exported using Solara.</div>
594
- <button id="uploadJsonBtn" style=" animation-delay: 0.5s;">Upload JSON</button>
595
- <div class="button-message" style=" animation-delay: 0.7s;">Alternatively, upload from a folder that includes the brand's JSON files.</div>
596
- <button id="uploadBrandBtn" style=" animation-delay: 0.9s;">Upload Folder</button>
597
- <div class="button-message" style=" animation-delay: 1.1s;">You also have the option to create new brand configurations.</div>
598
- <button id="newBrandBtn" style=" animation-delay: 1.3s;">New Brand</button>
633
+ <button id="uploadJsonBtn" style=" animation-delay: 0.5s;">Upload JSON</button>
634
+ <div class="button-message" style=" animation-delay: 0.7s;">Alternatively, upload from a folder that includes the brand's JSON files.</div>
635
+ <button id="uploadBrandBtn" style=" animation-delay: 0.9s;">Upload Folder</button>
636
+ <div class="button-message" style=" animation-delay: 1.1s;">You also have the option to create new brand configurations.</div>
637
+ <button id="newBrandBtn" style=" animation-delay: 1.3s;">New Brand</button>
599
638
  </div>
600
639
 
601
640
  </div>
@@ -43,13 +43,13 @@ class BrandLocalSource {
43
43
  }
44
44
  }
45
45
 
46
- async saveSection(key, configuration, brandKey) {
46
+ async saveSection(filename, configuration, brandKey) {
47
47
  if (this.savingInProgress) return;
48
48
  this.savingInProgress = true;
49
49
 
50
50
  const dataToSend = {
51
51
  brand_key: brandKey,
52
- key: key,
52
+ filename: filename,
53
53
  data: configuration
54
54
  };
55
55
 
@@ -5,7 +5,7 @@ class BrandRemoteSource {
5
5
  }
6
6
 
7
7
  async createNewBrandConfigurations() {
8
- const url = 'https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/develop/solara/lib/core/template/configurations.json';
8
+ const url = 'https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/configurations.json';
9
9
 
10
10
  try {
11
11
  const response = await fetch(url);
@@ -21,8 +21,8 @@ class BrandRemoteSource {
21
21
  }
22
22
  const content = await contentResponse.json();
23
23
  return {
24
- key: config.key,
25
- name: config.name,
24
+ filename: config.filename,
25
+ name: this.snakeToCapitalizedSpaced(config.filename, 'ios'),
26
26
  content: content
27
27
  };
28
28
  });
@@ -36,6 +36,22 @@ class BrandRemoteSource {
36
36
  }
37
37
  }
38
38
 
39
+ // TODO: should be ecnapsulated
40
+ snakeToCapitalizedSpaced(
41
+ snakeCaseString,
42
+ exclude = '',
43
+ transform = (item) => item.charAt(0).toUpperCase() + item.slice(1)
44
+ ) {
45
+ // Split by underscores
46
+ const parts = snakeCaseString.split('_').map(item => {
47
+ // Return the item as-is if it matches the exclude value
48
+ return item === exclude ? item : transform(item);
49
+ });
50
+
51
+ // Join the parts with a space
52
+ return parts.join(' ');
53
+ }
54
+
39
55
  async getBrandConfigurationsJsonFromDirectory(dirHandle) {
40
56
  const schema = await this.fetchBrandConfigurationsSchema();
41
57
 
@@ -72,6 +72,10 @@ class OnboardBrandBottomSheet extends HTMLElement {
72
72
  border-radius: 3.5px;
73
73
  font-size: 11.2px;
74
74
  background-color: var(--background-color);
75
+ <<<<<<< HEAD
76
+ =======
77
+ color: var(--text-color);
78
+ >>>>>>> develop
75
79
  }
76
80
  .tooltip {
77
81
  position: relative;
@@ -7,14 +7,14 @@ class EditSectionHandler < BaseHandler
7
7
  begin
8
8
  request_payload = JSON.parse(req.body)
9
9
  brand_key = request_payload['brand_key']
10
- key = request_payload['key']
10
+ filename = request_payload['filename']
11
11
  data = request_payload['data']
12
12
 
13
- update_section(key, data, brand_key)
13
+ update_section(filename, data, brand_key)
14
14
  set_current_brand_content_changed(brand_key, true)
15
15
 
16
16
  res.status = 200
17
- res.body = JSON.generate({ success: true, message: "Configuration for #{key} updated successfully" })
17
+ res.body = JSON.generate({ success: true, message: "Configuration for #{filename} updated successfully" })
18
18
  res.content_type = 'application/json'
19
19
  rescue JSON::ParserError => e
20
20
  handle_error(res, e, "Invalid JSON in request body", 400)
@@ -27,20 +27,8 @@ class EditSectionHandler < BaseHandler
27
27
  end
28
28
  end
29
29
 
30
- def update_section(key, data, brand_key)
31
- template = BrandConfigurationsManager.new(brand_key).template_with_key(key)
32
-
33
- path = template[:path]
34
-
35
- if File.exist?(path)
36
- File.write(path, JSON.pretty_generate(data))
37
- Solara.logger.debug("Updated Config for #{path}: #{data}")
38
- else
39
- raise "Config file not found: #{path}"
40
- end
41
- rescue StandardError => e
42
- Solara.logger.failure("Error updating section: #{e.message}")
43
- raise
30
+ def update_section(filename, data, brand_key)
31
+ BrandConfigUpdater.new.update(filename, data, brand_key)
44
32
  end
45
33
 
46
34
  def set_current_brand_content_changed(brand_key, changed)
@@ -26,18 +26,6 @@ class OnboardBrandHandler < BaseHandler
26
26
  end
27
27
  res.content_type = 'application/json'
28
28
  end
29
-
30
- def onboard_brand(brand_name, brand_key, clone_brand_key = nil)
31
- if BrandsManager.instance.exists(brand_key)
32
- return { success: false, message: "Brand with key (#{brand_key}) already added!" }
33
- end
34
- SolaraManager.new.onboard(brand_key, brand_name, clone_brand_key: clone_brand_key, open_dashboard: false)
35
- { success: true, message: "Brand added successfully" }
36
- rescue StandardError => e
37
- Solara.logger.failure("Error adding brand: #{e.message}")
38
- raise
39
- end
40
-
41
29
  end
42
30
 
43
31
  def onboard_brand(brand_name, brand_key, clone_brand_key = nil)
@@ -46,8 +34,5 @@ class OnboardBrandHandler < BaseHandler
46
34
  end
47
35
  SolaraManager.new.onboard(brand_key, brand_name, clone_brand_key: clone_brand_key, open_dashboard: false)
48
36
  { success: true, message: "Brand added successfully" }
49
- rescue StandardError => e
50
- Solara.logger.failure("Error adding brand: #{e.message}")
51
- raise
52
37
  end
53
38
  end