solara 0.5.0 → 0.7.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 (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