solara 0.4.0 → 0.6.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 (60) 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 +1 -1
  5. data/solara/lib/core/brands/brand_switcher.rb +92 -1
  6. data/solara/lib/core/dashboard/brand/BrandDetail.js +34 -2
  7. data/solara/lib/core/dashboard/brand/BrandDetailController.js +27 -234
  8. data/solara/lib/core/dashboard/brand/BrandDetailModel.js +14 -5
  9. data/solara/lib/core/dashboard/brand/BrandDetailView.js +16 -200
  10. data/solara/lib/core/dashboard/brand/SectionsFormManager.js +293 -0
  11. data/solara/lib/core/dashboard/brand/brand.html +223 -174
  12. data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +2 -5
  13. data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +36 -133
  14. data/solara/lib/core/dashboard/brands/Brands.js +31 -0
  15. data/solara/lib/core/dashboard/brands/BrandsController.js +0 -5
  16. data/solara/lib/core/dashboard/brands/BrandsView.js +2 -2
  17. data/solara/lib/core/dashboard/brands/brands.html +71 -52
  18. data/solara/lib/core/dashboard/component/AliasesBottomSheet.js +6 -6
  19. data/solara/lib/core/dashboard/component/BrandOptionsBottomSheet.js +4 -4
  20. data/solara/lib/core/dashboard/component/ConfirmationDialog.js +15 -10
  21. data/solara/lib/core/dashboard/component/EditJsonSheet.js +160 -0
  22. data/solara/lib/core/dashboard/component/MessageBottomSheet.js +5 -5
  23. data/solara/lib/core/dashboard/component/OnboardBrandBottomSheet.js +9 -3
  24. data/solara/lib/core/dashboard/handler/base_handler.rb +1 -0
  25. data/solara/lib/core/dashboard/handler/edit_section_handler.rb +1 -5
  26. data/solara/lib/core/dashboard/handler/onboard_brand_handler.rb +0 -15
  27. data/solara/lib/core/doctor/schema/brand_configurations.json +0 -8
  28. data/solara/lib/core/doctor/schema/platform/global/resources_manifest.json +30 -0
  29. data/solara/lib/core/doctor/schema/platform/json_manifest.json +39 -0
  30. data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +35 -1
  31. data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +30 -1
  32. data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +35 -1
  33. data/solara/lib/core/doctor/validator/template/template_validator.rb +9 -9
  34. data/solara/lib/core/scripts/brand_config_manager.rb +1 -1
  35. data/solara/lib/core/scripts/brand_configurations_manager.rb +41 -0
  36. data/solara/lib/core/scripts/code_generator.rb +342 -118
  37. data/solara/lib/core/scripts/file_manager.rb +11 -15
  38. data/solara/lib/core/scripts/file_path.rb +21 -1
  39. data/solara/lib/core/scripts/gitignore_manager.rb +12 -6
  40. data/solara/lib/core/scripts/json_manifest_processor.rb +136 -0
  41. data/solara/lib/core/scripts/platform/ios/infoplist_string_catalog_manager.rb +11 -1
  42. data/solara/lib/core/scripts/resource_manifest_processor.rb +151 -0
  43. data/solara/lib/core/scripts/solara_status_manager.rb +1 -1
  44. data/solara/lib/core/scripts/theme_generator.rb +21 -242
  45. data/solara/lib/core/solara_configurator.rb +1 -1
  46. data/solara/lib/core/template/brands/global/resources_manifest.json +10 -0
  47. data/solara/lib/core/template/brands/json/Json-Manifest.md +59 -0
  48. data/solara/lib/core/template/brands/json/json_manifest.json +16 -0
  49. data/solara/lib/core/template/brands/shared/theme.json +213 -29
  50. data/solara/lib/core/template/config/android_template_config.json +50 -0
  51. data/solara/lib/core/template/config/flutter_template_config.json +35 -0
  52. data/solara/lib/core/template/config/ios_template_config.json +50 -0
  53. data/solara/lib/core/template/configurations.json +46 -0
  54. data/solara/lib/core/template/project_template_generator.rb +2 -0
  55. data/solara/lib/solara/version.rb +1 -1
  56. data/solara/lib/solara.rb +19 -0
  57. data/solara/lib/solara_manager.rb +21 -13
  58. metadata +13 -4
  59. data/solara/lib/core/dashboard/component/AddFieldSheet.js +0 -175
  60. data/solara/lib/core/dashboard/handler/brand_configurations_manager.rb +0 -73
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b672162bbb4a247a016626afacc37b2deaab1dbde0c2f86e30a4ceedeb8a635d
4
- data.tar.gz: a0e53ed9ce61d791a50df3c292b1c096414903e7c573164e7553089048f289cc
3
+ metadata.gz: acd7dd411828a00b018a7ead20fc5f21853c45682a7b21f419377385b7e5cbb4
4
+ data.tar.gz: 187461f358743a466bcd31971622a32091fc4368264f51f73c7ef73b048ae6c3
5
5
  SHA512:
6
- metadata.gz: a22345201f6d5756cc2a54702dc8a0db0806d4fd1fd47f9f85dfde81545368908d22408345dae944bc004c4fe3507ceef563e6044cf7a90daad5a157efb1295f
7
- data.tar.gz: be3473d1802240cf75b4f934561e22302cbf93dee710dead6c0439b474e3b7c16ca858f3b647e480e04a493622ce9b8da488135179bca97caa808feedd1b2b18
6
+ metadata.gz: f3e85254b364be2888f9e9a760967bf64e4672df8ae4dde907b6f8a8928afa7266ad9a26f1ff35867ae8b7fb2fc362aa0be6f6a70c7aafb3b29e83fa2c456e72
7
+ data.tar.gz: ee5abac576555d1415bcaa33cec324bf21dcf0519b28eefc1687b6b129b6a74c1f160eb5f2b30f3037facda8ca163e0867c4a7acd8ecdfe2ef2ab88fcf84e330
data/solara/lib/.DS_Store CHANGED
Binary file
Binary file
@@ -12,7 +12,7 @@ class BrandOnboarder
12
12
  end
13
13
 
14
14
  def onboard
15
- if @clone_brand_key.nil? || @clone_brand_key.empty?
15
+ if @clone_brand_key.nil? || @clone_brand_key.strip.empty?
16
16
  generate_brand_template
17
17
  else
18
18
  clone_brand
@@ -30,6 +30,9 @@ class BrandSwitcher
30
30
  def switch
31
31
  BrandFontSwitcher.new(@brand_key).switch
32
32
 
33
+ ResourceManifestSwitcher.new(@brand_key, ignore_health_check: @ignore_health_check).switch
34
+ JsonManifestSwitcher.new(@brand_key).switch
35
+
33
36
  case @platform
34
37
  when Platform::Flutter
35
38
  IOSBrandSwitcher.new(@brand_key).switch
@@ -42,6 +45,94 @@ class BrandSwitcher
42
45
  else
43
46
  raise ArgumentError, "Invalid platform: #{@platform}"
44
47
  end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ class ResourceManifestSwitcher
54
+ def initialize(brand_key, ignore_health_check:)
55
+ @brand_key = brand_key
56
+ @ignore_health_check = ignore_health_check
57
+ end
58
+
59
+ def switch
60
+ Solara.logger.start_step("Process resource manifest: #{FilePath.resources_manifest}")
61
+ brand_resource_copier = ResourceManifestProcessor.new(@brand_key, ignore_health_check: @ignore_health_check)
62
+ brand_resource_copier.copy
63
+ Solara.logger.debug("#{@brand_key} resources copied successfully according to the manifest: #{FilePath.resources_manifest}.")
64
+ Solara.logger.end_step("Process resource manifest: #{FilePath.resources_manifest}")
65
+ end
66
+
67
+ end
68
+
69
+ class JsonManifestSwitcher
70
+ PLATFORM_CONFIGS = {
71
+ Platform::Flutter => {
72
+ language: Language::Dart,
73
+ output_path: :flutter_lib_artifacts
74
+ },
75
+ Platform::IOS => {
76
+ language: Language::Swift,
77
+ output_path: :ios_project_root_artifacts
78
+ },
79
+ Platform::Android => {
80
+ language: Language::Kotlin,
81
+ output_path: :android_project_java_artifacts
82
+ }
83
+ }.freeze
84
+
85
+ def initialize(brand_key)
86
+ @brand_key = brand_key
87
+ @brand_manifest_path = FilePath.brand_json_dir(brand_key)
88
+ @platform = SolaraSettingsManager.instance.platform
89
+ validate_inputs
90
+ end
91
+
92
+ def switch
93
+ with_logging do
94
+ process_manifests
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def validate_inputs
101
+ raise ArgumentError, "Brand key cannot be nil" if @brand_key.nil?
102
+ raise ArgumentError, "Invalid platform: #{@platform}" unless PLATFORM_CONFIGS.key?(@platform)
103
+ raise ArgumentError, "Manifest path not found: #{@brand_manifest_path}" unless File.exist?(@brand_manifest_path)
104
+ end
105
+
106
+ def with_logging
107
+ log_message = "Process JSON manifest: #{@brand_manifest_path}"
108
+ Solara.logger.start_step(log_message)
109
+ yield
110
+ Solara.logger.end_step(log_message)
111
+ rescue StandardError => e
112
+ Solara.logger.error("Failed to process manifest: #{e.message}")
113
+ raise
114
+ end
115
+
116
+ def process_manifests
117
+ config = PLATFORM_CONFIGS[@platform]
118
+ output_path = FilePath.send(config[:output_path])
119
+
120
+ [
121
+ @brand_manifest_path,
122
+ FilePath.brand_global_json_dir
123
+ ].each do |manifest_path|
124
+ process_manifest(config[:language], manifest_path, output_path)
125
+ end
126
+ end
127
+
128
+ def process_manifest(language, manifest_path, output_path)
129
+ JsonManifestProcessor.new(
130
+ manifest_path,
131
+ language,
132
+ output_path
133
+ ).process
134
+ rescue StandardError => e
135
+ raise StandardError, "Failed to process #{manifest_path}: #{e.message}"
45
136
  end
46
137
  end
47
138
 
@@ -225,7 +316,7 @@ class ThemeSwitcher
225
316
  def generate_theme(name, language, platform)
226
317
  Solara.logger.start_step("Generate #{name} for #{platform}")
227
318
 
228
- theme_manager = ThemeGeneratorManager.new(FilePath.brand_theme(@brand_key))
319
+ theme_manager = ThemeGenerator.new(FilePath.brand_theme(@brand_key))
229
320
  theme_manager.generate(language, FilePath.generated_config(name, platform))
230
321
 
231
322
  Solara.logger.end_step("Generate #{name} for #{platform}")
@@ -2,6 +2,37 @@ import BrandDetailModel from './BrandDetailModel.js';
2
2
  import BrandDetailView from './BrandDetailView.js';
3
3
  import BrandDetailController from './BrandDetailController.js';
4
4
 
5
+ const modeToggle = document.getElementById('modeToggle');
6
+ const body = document.body;
7
+ const icon = modeToggle.querySelector('i');
8
+
9
+ function applyMode(mode) {
10
+ if (mode === 'dark') {
11
+ body.classList.add('dark-mode');
12
+ icon.classList.remove('fa-sun');
13
+ icon.classList.add('fa-moon');
14
+ } else {
15
+ body.classList.remove('dark-mode');
16
+ icon.classList.remove('fa-moon');
17
+ icon.classList.add('fa-sun');
18
+ }
19
+ }
20
+
21
+ const savedMode = localStorage.getItem('mode');
22
+ if (savedMode) {
23
+ applyMode(savedMode);
24
+ } else {
25
+ const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
26
+ applyMode(systemPrefersDark ? 'dark' : 'light');
27
+ }
28
+
29
+ modeToggle.addEventListener('click', () => {
30
+ const currentMode = body.classList.contains('dark-mode') ? 'dark' : 'light';
31
+ const newMode = currentMode === 'dark' ? 'light' : 'dark';
32
+ applyMode(newMode);
33
+ localStorage.setItem('mode', newMode);
34
+ });
35
+
5
36
  window.onload = function () {
6
37
  document.getElementById('loadingOverlay').style.display = 'none';
7
38
  };
@@ -20,10 +51,11 @@ window.addEventListener('scroll', function () {
20
51
  }
21
52
  lastScrollTop = scrollTop;
22
53
  });
23
-
54
+
24
55
  document.addEventListener('DOMContentLoaded', async () => {
25
56
  const model = new BrandDetailModel();
26
57
  const view = new BrandDetailView(model);
27
58
  const controller = new BrandDetailController(model, view);
28
59
  await controller.initializeApp();
29
- });
60
+ });
61
+
@@ -1,15 +1,10 @@
1
1
  import {DataSource} from './BrandDetailModel.js';
2
- import InfoPlistStringCatalogManager from "./InfoPlistStringCatalogManager.js";
3
2
 
4
3
  class BrandDetailController {
5
4
  constructor(model, view) {
6
5
  this.model = model;
7
6
  this.view = view;
8
- this.onSectionChanged = this.onSectionChanged.bind(this);
9
- this.deleteField = this.deleteField.bind(this);
10
7
  this.initializeEventListeners();
11
- this.view.setOnSectionChangedHandler(this.onSectionChanged.bind(this));
12
- this.view.setOnDeleteFieldHandler(this.deleteField.bind(this));
13
8
  }
14
9
 
15
10
  async initializeApp() {
@@ -80,7 +75,7 @@ class BrandDetailController {
80
75
 
81
76
  async addNewBrand() {
82
77
  this.view.showOnboardBrandForm(async (key, name) => {
83
- const configurations = await this.model.createNewBrandConfogurations()
78
+ const configurations = await this.model.createNewBrandConfigurations()
84
79
  await this.addBrand(key, name, configurations)
85
80
  })
86
81
  }
@@ -96,11 +91,12 @@ class BrandDetailController {
96
91
  const response = await this.model.fetchBrandDetails();
97
92
  await this.onLoadSections(response.result);
98
93
  const {isCurrentBrand, contentChanged} = await this.model.fetchCurrentBrand();
94
+ this.model.isCurrentBrand = isCurrentBrand
99
95
 
100
- if (!isCurrentBrand) {
96
+ if (isCurrentBrand) {
97
+ this.view.setupSyncBrandButton(contentChanged ? '#ff4136' : '#4A90E2');
98
+ } else {
101
99
  this.view.showSwitchButton();
102
- } else if (contentChanged) {
103
- this.view.showApplyChangesButton();
104
100
  }
105
101
 
106
102
  await this.checkBrandHealth();
@@ -114,7 +110,7 @@ class BrandDetailController {
114
110
  try {
115
111
  this.view.addBrandOverlay.style.display = 'none'
116
112
  this.view.header.style.display = 'flex';
117
- this.view.updateAppNameTitle(`${configuraationsResult.brand.key} (${configuraationsResult.brand.name})`);
113
+ this.view.updateAppNameTitle(`${configuraationsResult.brand.name} - ${configuraationsResult.brand.key}`);
118
114
  await this.showSections(configuraationsResult);
119
115
  this.view.showIndex();
120
116
  } catch (error) {
@@ -128,7 +124,12 @@ class BrandDetailController {
128
124
  window.location.href = `../brands/brands.html?source=${this.model.source}`;
129
125
  });
130
126
 
131
- document.getElementById('applyChangesButton').addEventListener('click', () => this.switchToBrand());
127
+ document.getElementById('syncBrandButton').addEventListener(
128
+ 'click',
129
+ async () => {
130
+ await this.switchToBrand()
131
+ await this.view.toast("Synced successfully")
132
+ });
132
133
  document.getElementById('switchButton').addEventListener('click', () => this.switchToBrand());
133
134
 
134
135
  this.view.exportBrandBtn.addEventListener('click', () => this.exportBrand());
@@ -141,188 +142,39 @@ class BrandDetailController {
141
142
 
142
143
  const sectionItems = configuraationsResult.configurations
143
144
 
144
- for (let i = 0; i < sectionItems.length; i++) {
145
- const sectionData = sectionItems[i];
146
-
147
- if (sectionData.key === 'theme.json') {
148
- this.createThemeSections(sectionData)
149
- } else if (sectionData.key === 'InfoPlist.xcstrings') {
150
- this.createSection(
151
- sectionData.key,
152
- sectionData,
153
- new InfoPlistStringCatalogManager(sectionData.content).extractLocalizations(),
154
- sectionData.name,
155
- sectionData.inputType)
156
- } else {
157
- this.createSection(sectionData.key, sectionData, sectionData.content, sectionData.name, sectionData.inputType)
158
- }
159
- }
145
+ this.view.sectionsFormManager.display(
146
+ sectionItems,
147
+ (section, container) => {
148
+ this.onSectionChanged(section, container)
149
+ })
150
+
160
151
  } catch (error) {
161
152
  console.error('Error loading configurations:', error);
162
153
  alert(error.message);
163
154
  }
164
155
  }
165
156
 
166
- createThemeSections(sectionData) {
167
- this.createSection(`${sectionData.key}_colors`,
168
- sectionData,
169
- sectionData.content.colors,
170
- 'Theme Colors',
171
- 'color',
172
- 'colors')
173
- this.createSection(`${sectionData.key}_typography`,
174
- sectionData,
175
- sectionData.content.typography,
176
- 'Theme Typography',
177
- 'text',
178
- 'typography')
179
- this.createSection(`${sectionData.key}_spacing`,
180
- sectionData,
181
- sectionData.content.spacing,
182
- 'Theme Spacing', 'text',
183
- 'spacing')
184
- this.createSection(
185
- `${sectionData.key}_borderRadius`,
186
- sectionData,
187
- sectionData.content.borderRadius,
188
- 'Theme Border Radius',
189
- 'text',
190
- 'borderRadius')
191
- this.createSection(
192
- `${sectionData.key}_elevation`,
193
- sectionData,
194
- sectionData.content.elevation,
195
- 'Theme Elevation',
196
- 'text',
197
- 'elevation')
198
- }
199
-
200
- createSection(id, sectionData, content, sectionName, inputType, propertiesGroupName = null) {
201
- const sectionElement = this.view.createSection(sectionData.key, sectionName, inputType);
202
- sectionElement.id = id;
203
- sectionElement.dataset.propertiesGroupName = propertiesGroupName
204
-
205
- this.view.sectionsContainer.appendChild(sectionElement);
206
-
207
- this.view.populateJsonFields(sectionData, sectionElement, content, inputType);
157
+ async onSectionChanged(section, container) {
158
+ if (this.model.isRemote()) return
208
159
 
209
- const addButton = document.createElement('button');
210
- addButton.textContent = 'Add Field';
211
- addButton.className = 'add-field-btn';
212
- addButton.onclick = () => this.addNewField(sectionData, sectionElement, inputType);
213
- sectionElement.appendChild(addButton);
214
- }
215
-
216
- async onSectionChanged(sectionItem, sectionElement) {
217
160
  try {
218
- const configuration = await this.getSectionData(sectionElement.dataset.key)
219
- await this.model.saveSection(sectionItem, configuration);
220
- this.view.showApplyChangesButton();
161
+ await this.model.saveSection(container.dataset.key, section.content);
221
162
  await this.checkBrandHealth();
163
+ if (this.model.isCurrentBrand) {
164
+ await this.switchToBrand(true)
165
+ }
222
166
  } catch (error) {
223
167
  console.error('Error saving section:', error);
224
168
  alert(error.message);
225
169
  }
226
170
  }
227
171
 
228
- collectJsonData(container) {
229
- const data = {};
230
- const level = container.dataset.level
231
-
232
- const jsonObjects = container.querySelectorAll(`.json-object-${level}`);
233
- jsonObjects.forEach(jsonObject => {
234
- const jsonObjectLabel = jsonObject.querySelector('label').textContent;
235
- data[jsonObjectLabel] = this.collectJsonData(jsonObject);
236
-
237
- });
238
-
239
- const group = container.querySelectorAll(`.json-array-${level}`);
240
- group.forEach(arrayItem => {
241
- const label = arrayItem.querySelector('label').textContent;
242
- const items = arrayItem.querySelectorAll(`.json-array-item-${level}`);
243
- const indexed = arrayItem.querySelectorAll(`.json-array-item-indexed-${level}`).length !== 0;
244
-
245
- data[label] = Array.from(items).map((item, index) => {
246
- const values = this.collectJsonData(item)
247
- if (indexed) {
248
- return values[index]
249
- }
250
- return values;
251
- });
252
- });
253
-
254
- const result = this.collectJsonFields(container, level);
255
-
256
- Object.keys(data).forEach(key => {
257
- result[key] = data[key];
258
- });
259
- return result
260
- }
261
-
262
- collectJsonFields(container, level) {
263
- const result = {};
264
-
265
- const inputFields = container.querySelectorAll(`.json-field-${level}`);
266
- inputFields.forEach(inputField => {
267
- const values = this.getInputFieldsData(inputField);
268
- Object.keys(values).forEach(key => {
269
- result[key] = values[key];
270
- });
271
- })
272
- return result
273
- }
274
-
275
- getInputFieldsData(container) {
276
- const data = {};
277
-
278
- const inputs = container.querySelectorAll('input');
279
- inputs.forEach(input => {
280
- if (input.type === 'checkbox') {
281
- data[input.id] = input.checked;
282
- } else {
283
- let value = input.value;
284
- if (input.type === 'color') {
285
- value = `#${value.substring(1).toUpperCase()}`;
286
- } else if (!isNaN(value) && value.trim() !== '') {
287
- // Convert to number if it's a valid number string
288
- value = parseFloat(value);
289
- }
290
- data[input.id] = value;
291
- }
292
- });
293
-
294
- return data;
295
- }
296
-
297
- addNewField(sectionItem, sectionElement, inputType) {
298
- this.view.showAddFieldForm(sectionItem, sectionElement, inputType);
299
- }
300
-
301
- deleteField(sectionItem, fieldContainer) {
302
- this.view.showConfirmationDialog(
303
- 'Are you sure you want to delete this item?',
304
- () => {
305
- const sectionElement = fieldContainer.closest('.section');
306
- fieldContainer.remove();
307
- this.onSectionChanged(sectionItem, sectionElement);
308
- }
309
- );
310
- }
311
-
312
- getArrayValue(container) {
313
- const arrayItems = container.querySelectorAll('.array-item-input');
314
- return Array.from(arrayItems).map(item => item.value);
315
- }
316
-
317
- async switchToBrand() {
172
+ async switchToBrand(silentError = false) {
318
173
  try {
319
174
  await this.model.switchToBrand();
320
- const applyChangesButton = document.getElementById('applyChangesButton');
321
- applyChangesButton.style.display = 'none';
322
- location.reload();
323
175
  } catch (error) {
324
176
  console.error('Error switching to brand:', error);
325
- alert(error.message);
177
+ if (!silentError) alert(error.message);
326
178
  }
327
179
  }
328
180
 
@@ -350,72 +202,13 @@ class BrandDetailController {
350
202
  }
351
203
  }
352
204
 
353
- async getSectionData(sectionKey) {
354
- try {
355
- const container = this.view.sectionsContainer;
356
-
357
- const sectionElements = Array.from(container.querySelectorAll('.section'))
358
- .filter(element => element.dataset.key === sectionKey);
359
-
360
- if (sectionElements.length === 1) {
361
- return this.collectJsonData(sectionElements[0])
362
- }
363
-
364
- const configurations = sectionElements.map(sectionElement => {
365
- const propertiesGroupName = sectionElement.dataset.propertiesGroupName;
366
- const config = this.collectJsonData(sectionElement);
367
-
368
- if (propertiesGroupName !== "null") {
369
- return {[propertiesGroupName]: config};
370
- } else {
371
- return config;
372
- }
373
- });
374
-
375
- return configurations.reduce((acc, config) => {
376
- const key = Object.keys(config)[0]; // Get the key from the configuration item
377
- acc[key] = config[key]; // Assign the value to the dynamic object
378
- return acc;
379
- }, {});
380
-
381
- } catch (error) {
382
- console.error('Error downloading brand:', error);
383
- alert(error.message);
384
- }
385
- }
386
-
387
205
  async exportBrand() {
388
206
  try {
389
- const sectionsContainer = this.view.sectionsContainer;
207
+ const result = this.view.sectionsFormManager.data();
390
208
 
391
209
  const brandKey = this.view.sectionsContainer.dataset.brandKey;
392
210
  const brandName = this.view.sectionsContainer.dataset.brandName;
393
211
 
394
- const sectionElements = Array.from(sectionsContainer.querySelectorAll('.section'));
395
-
396
- const uniqueSections = new Map();
397
-
398
- // The theme section has been divided into multiple categories (e.g., colors, typography).
399
- // In the getSectionData function, we merge these sections. To prevent duplication when
400
- // processing the sections, we will ensure that each section key is processed only once.
401
- await Promise.all(sectionElements.map(async sectionElement => {
402
- const key = sectionElement.dataset.key;
403
- // Check if the key already exists in the map
404
- if (!uniqueSections.has(key)) {
405
- const configurations = await this.getSectionData(key);
406
- uniqueSections.set(
407
- key, {
408
- key: key,
409
- name: sectionElement.dataset.name,
410
- inputType: sectionElement.dataset.inputType,
411
- content: configurations
412
- });
413
- }
414
- }));
415
-
416
- // Convert the map values to an array
417
- const result = Array.from(uniqueSections.values());
418
-
419
212
  this.model.exportBrand(brandKey, brandName, result);
420
213
 
421
214
  } catch (error) {
@@ -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
 
@@ -17,6 +18,14 @@ class BrandDetailModel {
17
18
  this.source = sourceFromUrl === DataSource.LOCAL ? DataSource.LOCAL : DataSource.REMOTE;
18
19
  }
19
20
 
21
+ isLocal() {
22
+ return this.source === DataSource.LOCAL
23
+ }
24
+
25
+ isRemote() {
26
+ return this.source === DataSource.REMOTE
27
+ }
28
+
20
29
  getQueryFromUrl(name) {
21
30
  return this.localSource.getQueryFromUrl(name);
22
31
  }
@@ -29,10 +38,10 @@ class BrandDetailModel {
29
38
  return await this.localSource.fetchCurrentBrand(this.brandKey);
30
39
  }
31
40
 
32
- async saveSection(sectionItem, configuration) {
41
+ async saveSection(key, configuration) {
33
42
  switch (this.source) {
34
43
  case DataSource.LOCAL:
35
- return await this.localSource.saveSection(sectionItem, configuration, this.brandKey);
44
+ return await this.localSource.saveSection(key, configuration, this.brandKey);
36
45
  case DataSource.REMOTE:
37
46
  // Saving is not supported remotely. Instead, user can export the brand.
38
47
  return
@@ -48,7 +57,7 @@ class BrandDetailModel {
48
57
  async checkBrandHealth() {
49
58
  switch (this.source) {
50
59
  case DataSource.LOCAL:
51
- return await this.localSource.checkBrandHealth(this.brandKey);
60
+ return await this.localSource.checkBrandHealth(this.brandKey);
52
61
  case DataSource.REMOTE:
53
62
  // Checking health is not supported remotely yet.
54
63
  return
@@ -57,8 +66,8 @@ class BrandDetailModel {
57
66
  }
58
67
  }
59
68
 
60
- async createNewBrandConfogurations() {
61
- return await this.remoteSource.createNewBrandConfogurations();
69
+ async createNewBrandConfigurations() {
70
+ return await this.remoteSource.createNewBrandConfigurations();
62
71
  }
63
72
 
64
73
  async createBrandConfigurationsFromDirectory(dirHandle) {